创建具有额外功能的自定义namedtuple类型

时间:2017-02-09 20:30:19

标签: python metaprogramming namedtuple overrides

我想创建我自己的内置命名元组类型,它具有一些额外的功能。我们假设我们创建了一个类:

from collections import namedtuple
MyClass = namedtuple('MyClass', 'field1 field2')

它是不可变的,可读的和简单的。现在我可以创建MyClass的实例:

myobj = MyClass(field1 = 1, field2 = 3.0)
print(myobj.field1, myobj.field2)

我的额外要求是在创建实例时我想检查field1int类型而field2float。例如,如果用户尝试创建MyClass实例:

obj = MyClass(field1 = 1, field2 = 3.0) # instantiates ok
obj1 = MyClass(field1 = 'sometext', field2 = 3.0) # raises TypeError

我尝试制作一个可以验证数据类型(MyClass应该是不可变的)的自定义命名元素,例如。:

MyClass = modifiednamedtuple('MyClass', 'field1 field2', (int, float) )

但卡住了:(。namedtuple是函数(不能是modifiednamedtuple的基类),我的元类实验失败了。

任何提示或建议?

好吧,我想出了一个可能不是"清洁"或pythonic。除了我的对象不是不可变的外,它的工作原理。如何使它们不变?有任何建议如何让它更干净和更红?

这是我的代码。:

def typespecificnamedtuple(name, *attr_definitions):

    def init(self, *args, **kwargs):
        valid_types = dict(attr_definitions) # tuples2dict
        for attr_name, value in kwargs.items():
            valid_type = valid_types[attr_name]
            if not isinstance(value, valid_type):
                raise TypeError('Cannot instantiate class '+ self.__name__+
                    '. Inproper datatype for '+ attr_name + '=' + str(value)+ 
                        ', expected '+str(valid_type) )
            setattr(self, attr_name, value)


    class_dict = {'__init__' : init, '__name__' : name}
    for attr_def in attr_definitions:
        class_dict[attr_def[0]] = attr_def[1] # attr_def is ('name', <type int>)

    customType = type(name, (object, ), class_dict )
    return customType

if __name__ == '__main__':
    MyClass = typespecificnamedtuple('MyClass', ('value', int), ('value2', float)  )
    mc = MyClass(value = 1, value2 = 3.0)
    mc.something = 1    # this assigment is possible :( how to make immutable?
    print(mc.__name__, mc.value, mc.value2, mc.something)
    mc1 = MyClass(value = 1, value2 = 'sometext')   # TypeError exception is raised

和控制台输出。:

MyClass 1 3.0 1
Traceback (most recent call last):
  File "/home/pawel/workspace/prices/prices.py", line 89, in <module>
    mc1 = MyClass(value = 1, value2 = 'sometext')   # TypeError exception is raised
  File "/home/pawel/workspace/prices/prices.py", line 70, in init
    ', expected '+str(valid_type) )
TypeError: Cannot instantiate class MyClass. Inproper datatype for value2=sometext, expected <class 'float'>

2 个答案:

答案 0 :(得分:3)

你注意到,

namedtuple不是一个班级;这是一个功能。但它是一个返回类的函数。因此,您可以将namedtuple调用的结果用作父类。

由于它是不可变的,namedtuple初始化在__new__而不是__init__

这样的事情,或许是:

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))

答案 1 :(得分:1)

namedtuple()使用string template生成类对象。

您可以对修改后的版本使用相同的技术;但是请使用已为您生成的代码作为基类:

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

这使您可以生成新的类型检查namedtuple类:

>>> 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'