How is a function called inside a class declaration?

Enter this code:

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

Obviously, calling zope.interface.implements somehow modifies the properties and behavior of the Foo class.

How does this happen? How to use this approach in my code?

Sample code is part of zope.interface .

+3
source share
2 answers

Detailed "what's going on"

The zope.interface.implements() function checks the stack of frames and changes the namespace of the locals() (python dict ) class in the construct. Everything inside the class statement in python is executed in this namespace, and the result forms the body of the class.

The function adds an extra value to the class namespace, __implements_advice_data__ with some data (the interfaces you passed to the function, and classImplements called, which will be used later.

Then it either adds or chains in the metaclass for the class in question, adding (or changing a pre-existing) __metaclass__ key in the namespace. This ensures that in the future, every time you create an instance of the class, the metaclass that is now installed will be called first.

In fact, this metaclass (class advisor) is a little insidious; it is deleted again after the first instantiation. It simply calls the callback specified in __implements_advice_data__ , together with the interfaces that you passed to the original implements() function, right after it either removes the __metaclass__ key from the class, or replaces it with the original __metaclass__ (which it called to create an instance of the first class ) The callback clears after itself, it removes the __implements_advice_data__ attribute from the class.

Short version

So all the work of zope.interface.implements() is done:

  • Add the passed interfaces along with the callback to the special attribute in the class ( __implements_advice_data__ ).
  • Ensures that the callback is called the first time an instance is created using a special metaclass.

After all, this is the moral equivalent of defining your interfaces:

 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) 

except that the last call is deferred until you create an instance of Foo .

But why go to such lengths?

When zope.interface was first developed, Python did not yet have class decorators.

zope.interface.classImplements() needs to be called separately as a function after creating the class, and calling zope.interface.implements() inside the class body provides better documentation about which interfaces the class implements. You can place it at the top of the class declaration, and everyone can see this important information when viewing the class. The presence of a classImplements() call located after the class declaration is not so noticeable and understandable, and it is easy to miss it to define a long class.

PEP 3129 finally added class decorators to the language, and they were added in python 2.6 and 3.0; zope.interface was first developed during python 2.3 (IIRC).

Now that we have the class decorators, zope.interface.implements() deprecated, and you can use the zope.interface.implementer class decorator instead:

 @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 
+6
source

Read the source, luke :

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) 
+2
source

Source: https://habr.com/ru/post/1446437/


All Articles