How does the function that is called inside the class declaration?

前端 未结 2 876
-上瘾入骨i
-上瘾入骨i 2020-12-19 19:18

Have this code:

>>> class Foo:
...     zope.interface.implements(IFoo)
...
...     def __init__(self, x=None):
...         self.x = x
...
...     de         


        
相关标签:
2条回答
  • 2020-12-19 19:36

    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
    
    0 讨论(0)
  • 2020-12-19 19:42

    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)
    
    0 讨论(0)
提交回复
热议问题