Как работает функция, которая вызывается внутри объявления класса?
Иметь этот код:
>>> class Foo:
... zope.interface.implements(IFoo)
...
... def __init__(self, x=None):
... self.x = x
...
... def bar(self, q, r=None):
... return q, r, self.x
...
... def __repr__(self):
... return "Foo(%s)" % self.x
Очевидно, зов zope.interface.implements
каким-то образом изменяет свойства и поведение класса Foo
,
Как это произошло? Как мне использовать этот подход в моем коде?
Пример кода является частью модуля zope.interface.
2 ответа
Подробное "что происходит"
zope.interface.implements()
Функция проверяет стек кадров и изменяет locals()
пространство имен (питон dict
) из класса в строительстве. Все в пределах class
Оператор в Python выполняется в этом пространстве имен, и результат формирует тело класса.
Функция добавляет дополнительное значение в пространство имен класса, __implements_advice_data__
с некоторыми данными (интерфейсы, которые вы передали в функцию, и classImplements
вызываемый, то, что будет использовано позже.
Затем он добавляет или цепочки в метаклассе для рассматриваемого класса, добавляя (или изменяя ранее существовавший) __metaclass__
введите пространство имен. Это гарантирует, что в будущем, каждый раз, когда вы создаете экземпляр класса, теперь будет вызываться установленный метакласс.
На самом деле, этот метакласс (советник по классам) немного обманчив; он удаляет себя снова после первого создания экземпляра. Он просто вызывает обратный вызов, указанный в __implements_advice_data__
вместе с интерфейсами, которые вы передали в оригинал implements()
Функция сразу после удаления __metaclass__
ключ от класса, или заменяет его оригиналом __metaclass__
(который вызывается для создания первого экземпляра класса). Обратный вызов очищает после себя, он удаляет __implements_advice_data__
атрибут из класса.
Короткая версия
В итоге все работы zope.interface.implements()
делает это:
- Добавьте переданные интерфейсы вместе с обратным вызовом к специальному атрибуту в классе (
__implements_advice_data__
). - Гарантирует, что обратный вызов вызывается при первом создании экземпляра с использованием специального метакласса.
В конце концов, это моральный эквивалент определения ваших интерфейсов следующим образом:
class Foo:
def __init__(self, x=None):
self.x = x
def bar(self, q, r=None):
return q, r, self.x
def __repr__(self):
return "Foo(%s)" % self.x
zope.interface.classImplements(Foo, IFoo)
за исключением того, что последний вызов откладывается до тех пор, пока вы сначала не создадите экземпляр Foo
,
Но зачем идти на такие меры?
когда zope.interface
был впервые разработан, Python еще не имел декораторов классов.
zope.interface.classImplements()
должен быть вызван отдельно, как функция, после того, как класс был создан, и zope.interface.implements()
Вызов в теле класса обеспечивает лучшую документацию о том, какие интерфейсы реализует класс. Вы можете поместить его прямо вверху объявления класса, и каждый сможет увидеть эту важную информацию, глядя на класс. Иметь classImplements()
Вызов, расположенный после объявления класса, не так хорошо виден и ясен, а для длинных определений классов его легко будет вообще пропустить.
PEP 3129, наконец, добавил к языку декораторы классов, и они были добавлены в python 2.6 и 3.0; zope.interface
Впервые был разработан еще во времена Python 2.3 (IIRC).
Теперь, когда у нас есть декораторы классов, zope.interface.implements()
устарела, и вы можете использовать zope.interface.implementer
вместо декоратора класса:
@zope.interface.implementer(IFoo)
class Foo:
def __init__(self, x=None):
self.x = x
def bar(self, q, r=None):
return q, r, self.x
def __repr__(self):
return "Foo(%s)" % self.x
http://svn.zope.org/zope.interface/trunk/src/zope/interface/declarations.py?rev=124816&view=markup
def _implements(name, interfaces, classImplements): frame = sys._getframe(2) locals = frame.f_locals # Try to make sure we were called from a class def. In 2.2.0 we can't # check for __module__ since it doesn't seem to be added to the locals # until later on. if (locals is frame.f_globals) or ( ('__module__' not in locals) and sys.version_info[:3] > (2, 2, 0)): raise TypeError(name+" can be used only from a class definition.") if '__implements_advice_data__' in locals: raise TypeError(name+" can be used only once in a class definition.") locals['__implements_advice_data__'] = interfaces, classImplements addClassAdvisor(_implements_advice, depth=3)