2017-02-09 70 views
3

我想创建自己的具有一些额外功能的build-in namedtuple类型。假设我们创建一个类:创建具有额外功能的自定义namedtuple类型

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 

我试图做一个定制namedtuple可以验证数据类型(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'> 
+0

这是什么',从那个namedtuple'要获取?尺寸?不变性?验证属性值的典型方法是使用*属性*,但工厂函数在这里可能更直接。 – jonrsharpe

+0

你想要*生成修改后的命名元组*还是只有一个类足够?换句话说,'namedtuple'是一个类工厂,应该'modifiednamedtuple'是一个类工厂* –

回答

2

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

嘿,简单而干净,足以解决我的问题:)。而__new__方法代替了我的代码中的__init__ ....我看到的唯一小缺点是您已将类定义(namedtuple)与其特殊功能分开(所有字段名必须在MyClass中重复以检查数据类型)。谢啦! –

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

嘿,这个字符串模板和exec作为类生成器的好东西。漂亮的东西专门的大众类型的一代。我今天学到了东西。谢谢 –

+0

@MaciejskiPawel:很高兴有帮助!如果您觉得它对您有用,请随时[接受我的回答](http://meta.stackexchange.com/questions/5234/how-does-accepting-an-answer-work)。 :-) –

相关问题