create custom namedtuple type with extra features

后端 未结 2 1241
死守一世寂寞
死守一世寂寞 2021-01-18 16:19

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         


        
相关标签:
2条回答
  • 2021-01-18 16:48

    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'
    
    0 讨论(0)
  • 2021-01-18 17:04

    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))
    
    0 讨论(0)
提交回复
热议问题