I\'d like to create my own type of build-in namedtuple that has some extra features. Let\'s say we create a class:
from collections import namedtuple
MyClass
namedtuple()
uses a string template to generate a class object.
You could use that same technique for your modified version; but do use the code already generated for you as a base class:
import sys
from collections import OrderedDict
_typechecking_class_template = """\
from collections import namedtuple as _namedtuple
class {typename}(_namedtuple({typename!r}, {field_names!r})):
'{typename}({arg_list})'
__slots__ = ()
def __new__(_cls, {arg_list}):
'Create new instance of {typename}({arg_list})'
for name, type_ in _cls._field_types.items():
value = locals()[name]
if not isinstance(value, type_):
raise TypeError("Incorrect type {{!r}} for {{}}, expected {{!r}}".format(
type(value).__name__, name, type_.__name__))
return tuple.__new__(_cls, ({arg_list}))
"""
def typechecking_namedtuple(typename, field_names, field_types):
if isinstance(field_names, str):
field_names = field_names.replace(',', ' ').split()
field_names = list(map(str, field_names))
typename = str(typename)
class_definition = _typechecking_class_template.format(
typename = typename,
field_names = tuple(field_names),
arg_list = repr(tuple(field_names)).replace("'", "")[1:-1],
)
namespace = dict(__name__='typechecking_namedtuple_%s' % typename)
exec(class_definition, namespace)
result = namespace[typename]
result._field_types = OrderedDict(zip(field_names, field_types))
try:
module = sys._getframe(1).f_globals.get('__name__', '__main__')
result.__module__ = module
except (AttributeError, ValueError):
pass
return result
This lets you produce new type-checking namedtuple classes:
>>> MyClass = typechecking_namedtuple('MyClass', 'field1 field2', (int, float))
>>> MyClass(42, 81.2)
MyClass(field1=42, field2=81.2)
>>> MyClass('fourtytwo', 81.2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 16, in __new__
TypeError: Incorrect type 'str' for field1, expected 'int'
>>> MyClass(42, None)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 16, in __new__
TypeError: Incorrect type 'NoneType' for field2, expected 'float'
namedtuple
isn't a class, as you note; it's a function. But it's a function that returns a class. Thus, you can use the result of the namedtuple
call as a parent class.
Since it is immutable, a namedtuple
is initialized in __new__
rather in in __init__
.
So something like this, perhaps:
MyTuple = namedtuple('MyTuple', 'field1 field2')
class MyClass(MyTuple):
def __new__(cls, field1, field2):
if not isinstance(field1, int):
raise TypeError("field1 must be integer")
# accept int or float for field2 and convert int to float
if not isinstance(field1, (int, float)):
raise TypeError("field2 must be float")
return MyTuple.__new__(cls, field1, float(field2))