Have 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, the call of zope.interface.implements
in some way alters the properties and behavior of the class Foo
.
How does this happen? How do I use this approach in my code?
Example code is the part of zope.interface module.
The detailed "what happens"
The zope.interface.implements()
function inspects the frame stack and alters the locals()
namespace (a python dict
) of the class-in-construction. Everything within a class
statement in python is executed in that namespace, and the result forms the class body.
The function adds an extra value to the class namespace, __implements_advice_data__
with some data (the interfaces you've passed to the function, and the classImplements
callable, something that'll be used later on.
It then either adds or chains in a metaclass for the class in question, by adding the (or altering 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 now installed will be called first.
In fact, this metaclass (the class advisor) is a little devious; it removes itself again after the first time you create an instance. It simply calls the callback specified in the __implements_advice_data__
together with the interfaces you passed to the original implements()
function, right after it either deletes the __metaclass__
key from the class, or replaces it with the original __metaclass__
(which it called to create the first class instance). The callback cleans up after itself, it removes the __implements_advice_data__
attribute from the class.
The short version
In summary, all the work zope.interface.implements()
does is:
- Add the passed interfaces, together with a callback to a special attribute in the class (
__implements_advice_data__
).
- Ensures that the callback is called the first time you create an instance, using a special metaclass.
In the end, it's the moral equivalent of defining your interfaces like this:
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 postponed until you first 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 the class has been created, and a zope.interface.implements()
call within the class body provides better documentation about what interfaces a class implements. You can place it right at the top of a class declaration, and everyone can see this important piece of information when looking at the class. Having a classImplements()
call located after the class declaration is not nearly as visible and clear, and for long class definitions it is easily going to be missed altogether.
PEP 3129 finally did add class decorators to the language, and they were added to python 2.6 and 3.0; zope.interface
was first developed back in the days of python 2.3 (IIRC).
Now that we do have class decorators, zope.interface.implements()
has been 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
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)