2017-02-09 48 views
3

Я хотел бы создать собственный тип встроенного namedtuple, который имеет некоторые дополнительные функции. Предположим, мы создали класс:создать собственный тип namedtuple с дополнительными функциями

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

Это неизменный, читаемый и простой. Теперь я могу создавать экземпляры MyClass:

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

Моего дополнительное требование при создании экземпляра я хотел бы проверить, если field1 является int типа и field2 является float. Например, если пользователь попытается создать 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 является функцией (не может быть BaseClass для modifiednamedtuple), мои эксперименты с метаклассами не удалось.

Любые советы или предложения?

ОК, я придумал решение, которое может быть не «чистым» или питоническим. Он работает, за исключением того, что мои объекты не являются неизменными. Как сделать их неизменными? Любые предложения, как сделать его более чистым и переконфигурированным?

Вот мой код .:

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 как генератор классов. Хороший материал для генерации массовых типов ad-hoc. Сегодня я чему-то научился. Спасибо –

+0

@MaciejskiPawel: Рад помочь! Не стесняйтесь [принять мой ответ] (http://meta.stackexchange.com/questions/5234/how-does-accepting-an-answer-work), если вы считаете, что это было полезно для вас. :-) –

 Смежные вопросы

  • Нет связанных вопросов^_^