2015-10-12 9 views
2

Я использую метакласса создать свойство для новых классов, как это:Python: Как создать атрибут экземпляра метакласса?

class Property(object): 
    def __init__(self, internal_name, type_, default_value): 
     self._internal_name = internal_name 
     self._type = type_ 
     self._default_value = default_value 

    def generate_property(self): 
     def getter(object_): 
      return getattr(object_, self._internal_name) 
     def setter(object_, value): 
      if not isinstance(value, self._type): 
       raise TypeError("Expect type {0}, got {1}.".format(self._type, type(value))) 
      else: 
       setattr(object_, self._internal_name, value) 
     return property(getter, setter) 

class AutoPropertyMeta(type): 
    def __new__(cls, name, bases, attributes): 
     for name, value in attributes.iteritems(): 
      if isinstance(value, Property): 
       attributes[name] = value.generate_property() 
     return super(AutoPropertyMeta, cls).__new__(cls, name, bases, attributes) 

Таким образом, я могу написать код так:

class SomeClassWithALotAttributes(object): 
    __metaclass__ = AutoPropertyMeta 
    attribute_a = Property("_attribute_a", int, 0) 
    ... 
    attribute_z = Property("_attribute_z", float, 1.0) 

вместо:

class SomeClassWithALotAttributes(object): 
    def __init__(self): 
     self._attribute_a = 0 
     ... 
     self._attribute_z = 1.0 

    def get_attribute_a(self): 
     return self._attribute_a 

    def set_attribute_a(self, value): 
     if not isinstance(value, int): 
      raise TypeError("Expect type {0}, got {1}.".format(self._type, type(value)) 
     else: 
      self._attribute_a = value 

    attribute_a = property(get_attribute_a, set_attribute_a) 
    ... 

Он отлично работает, если вы всегда задаете значение перед получением значения атрибута, поскольку AutoPropertyMeta генерирует только getter и setter. Фактический атрибут экземпляра создается, когда вы устанавливаете значение в первый раз. Поэтому я хочу знать, есть ли способ создать атрибут экземпляра для класса по метаклассу.

Вот обходной путь я использую в настоящее время, но я всегда задаюсь вопросом, если есть лучший способ:

class Property(object): 
    def __init__(self, internal_name, type_, default_value): 
     self._internal_name = internal_name 
     self._type = type_ 
     self._default_value = default_value 

    def generate_property(self): 
     def getter(object_): 
      return getattr(object_, self._internal_name) 
     def setter(object_, value): 
      if not isinstance(value, self._type): 
       raise TypeError("Expect type {0}, got {1}.".format(self._type, type(value))) 
      else: 
       setattr(object_, self._internal_name, value) 
     return property(getter, setter) 

    def generate_attribute(self, object_): 
     setattr(object_, self._internal_name, self._default_value) 

class AutoPropertyMeta(type): 
    def __new__(cls, name, bases, attributes): 
     property_list = [] 
     for name, value in attributes.iteritems(): 
      if isinstance(value, Property): 
       attributes[name] = value.generate_property() 
       property_list.append(value) 
     attributes["_property_list"] = property_list 
     return super(AutoPropertyMeta, cls).__new__(cls, name, bases, attributes) 

class AutoPropertyClass(object): 
    __metaclass__ = AutoPropertyMeta 
    def __init__(self): 
     for property_ in self._property_list: 
      property_.generate_attribute(self) 

class SomeClassWithALotAttributes(AutoPropertyClass): 
    attribute_a = Property("_attribute_a", int, 0) 
+0

Вы не можете создать атрибут экземпляра без создания экземпляра. Почему бы вам просто не ввести код в getter, который проверяет, существует ли атрибут и возвращает значение по умолчанию, если нет? – BrenBarn

+0

вы можете назначить значение по умолчанию для атрибута раньше времени, нет? – HuStmpHrrr

+0

@BrenBarn Я однажды попытался создать атрибут в getter, если атрибут не существует. Но таким образом мне придется проверять, существует ли атрибут каждый раз, когда вызывается получатель. Поэтому я перешел к обходному решению, которое я опубликовал в последнем вопросе. – kkpattern

ответ

1

Вот пример того, что я имел в виду инъекционного новый __init__. Пожалуйста, имейте в виду, что это просто весело, и вы не должны этого делать.

class Property(object): 
    def __init__(self, type_, default_value): 
     self._type = type_ 
     self._default_value = default_value 

    def generate_property(self, name): 
     self._internal_name = '_' + name 
     def getter(object_): 
      return getattr(object_, self._internal_name) 
     def setter(object_, value): 
      if not isinstance(value, self._type): 
       raise TypeError("Expect type {0}, got {1}.".format(self._type, type(value))) 
      else: 
       setattr(object_, self._internal_name, value) 
     return property(getter, setter) 

class AutoPropertyMeta(type): 
    def __new__(meta, name, bases, attributes): 
     defaults = {} 
     for name, value in attributes.iteritems(): 
      if isinstance(value, Property): 
       attributes[name] = value.generate_property(name) 
       defaults[name] = value._default_value 
     # create __init__ to inject into the class 
     # our __init__ sets up our secret attributes 
     if '__init__' in attributes: 
      realInit = attributes['__init__'] 
      # we do a deepcopy in case default is mutable 
      # but beware, this might not always work 
      def injectedInit(self, *args, **kwargs): 
       for name, value in defaults.iteritems(): 
        setattr(self, '_' + name, copy.deepcopy(value)) 
       # call the "real" __init__ that we hid with our injected one 
       realInit(self, *args, **kwargs) 
     else: 
      def injectedInit(self, *args, **kwargs): 
       for name, value in defaults.iteritems(): 
        setattr(self, '_' + name, copy.deepcopy(value)) 
     # inject it 
     attributes['__init__'] = injectedInit 
     return super(AutoPropertyMeta, meta).__new__(meta, name, bases, attributes) 

Тогда:

class SomeClassWithALotAttributes(object): 
    __metaclass__ = AutoPropertyMeta 
    attribute_a = Property(int, 0) 
    attribute_z = Property(list, [1, 2, 3]) 

    def __init__(self): 
     print("This __init__ is still called") 

>>> x = SomeClassWithALotAttributes() 
This __init__ is still called 
>>> y = SomeClassWithALotAttributes() 
This __init__ is still called 
>>> x.attribute_a 
0 
>>> y.attribute_a 
0 
>>> x.attribute_a = 88 
>>> x.attribute_a 
88 
>>> y.attribute_a 
0 
>>> x.attribute_z.append(88) 
>>> x.attribute_z 
[1, 2, 3, 88] 
>>> y.attribute_z 
[1, 2, 3] 
>>> x.attribute_z = 88 
Traceback (most recent call last): 
    File "<pyshell#76>", line 1, in <module> 
    x.attribute_z = 88 
    File "<pyshell#41>", line 12, in setter 
    raise TypeError("Expect type {0}, got {1}.".format(self._type, type(value))) 
TypeError: Expect type <type 'list'>, got <type 'int'>. 

Идея заключается в том, чтобы написать свой собственный __init__, что делает инициализацию секретных атрибутов. Затем вы вводите его в пространство имен классов перед созданием класса, но храните ссылку на оригинал __init__ (если есть), чтобы вы могли его вызвать, когда это необходимо.

+0

Также я хочу знать, почему не следует использовать инъецируемый «__init__» трюк? (НЕ рассматривайте проблему с изменяемой копией, мы можем решить эту проблему по умолчанию, вместо значения по умолчанию). Что опасно? – kkpattern

+0

@kkpattern: Потому что это бесполезно сбивает с толку и на самом деле не дает вам ничего, кроме как выполнить проверку внутри получателя. Также я не удивлюсь, если есть крайние случаи, когда он может потерпеть неудачу (например, если какой-либо подкласс вызывает 'super()' в '__init__' и/или класс с этим метаклассом не находится в верхней части дерева наследования) , – BrenBarn

+0

Когда приходит к наследованию, действительно опасно вводить '__init__'. Теперь я думаю, что сделать проверку внутри getter или создать атрибуты экземпляра с помощью базового класса является приемлемым способом. Но все же счастлив узнать, как вводить инъекции. Я думаю, что все еще в порядке, чтобы принять этот ответ? – kkpattern

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