开发者

Metaclasses in Python: a couple of questions to clarify

After crashing with metaclasses i delved into the topic of metaprogramming in Python and I have a couple of questions that are, imho, n开发者_如何学编程ot clearly anwered in available docs.

  1. When using both __new__ and __init__ in a metaclass, their arguments must be defined the same?
  2. What's most efficient way to define class __init__ in a metaclass?
  3. Is there any way to refer to class instance (normally self) in a metaclass?


  1. When using both __new__ and __init__ in a metaclass, their arguments must be defined the same?

    I think Alex Martelli explains it most succinctly:

    class Name(Base1,Base2): <<body>>
        __metaclass__==suitable_metaclass
    

    means

    Name = suitable_metaclass('Name', (Base1,Base2), <<dict-built-by-body>>)
    

    So stop thinking about suitable_metaclass as a metaclass for a moment and just regard it as a class. Whenever you see

    suitable_metaclass('Name', (Base1,Base2), <<dict-built-by-body>>)
    

    it tells you that suitable_metaclass's __new__ method must have a signature something like

    def __new__(metacls, name, bases, dct)
    

    and a __init__ method like

    def __init__(cls, name, bases, dct)
    

    So the signatures are not exactly the same, but they differ only in the first argument.

  2. What's most efficient way to define class __init__ in a metaclass?

    What do you mean by efficient? It is not necessary to define the __init__ unless you want to.

  3. Is there any way to refer to class instance (normally self) in a metaclass?

    No, and you should not need to. Anything that depends on the class instance should be dealt with in the class definition, rather than in the metaclass.


For 1: The __init__ and __new__ of any class have to accept the same arguments, because they would be called with the same arguments. It's common for __new__ to take more arguments that it ignores (e.g. object.__new__ takes any arguments and it ignores them) so that __new__ doesn't have to be overridden during inheritance, but you usually only do that when you have no __new__ at all.

This isn't a problem here, because as it was stated, metaclasses are always called with the same set of arguments always so you can't run into trouble. With the arguments at least. But if you're modifying the arguments that are passed to the parent class, you need to modify them in both.

For 2: You usually don't define the class __init__ in a metaclass. You can write a wrapper and replace the __init__ of the class in either __new__ or __init__ of the metaclass, or you can redefine the __call__ on the metaclass. The former would act weirdly if you use inheritance.

import functools

class A(type):
    def __call__(cls, *args, **kwargs):
        r = super(A, cls).__call__(*args, **kwargs)
        print "%s was instantiated" % (cls.__name__, )
        print "the new instance is %r" % (r, )
        return r


class B(type):
    def __init__(cls, name, bases, dct):
        super(B, cls).__init__(name, bases, dct)
        if '__init__' not in dct:
            return
        old_init = dct['__init__']
        @functools.wraps(old_init)
        def __init__(self, *args, **kwargs):
            old_init(self, *args, **kwargs)
            print "%s (%s) was instantiated" % (type(self).__name__, cls.__name__)
            print "the new instance is %r" % (self, )
        cls.__init__ = __init__


class T1:
    __metaclass__ = A

class T2:
    __metaclass__ = B
    def __init__(self): 
        pass

class T3(T2):
    def __init__(self):
        super(T3, self).__init__()

And the result from calling it:

>>> T1()
T1 was instantiated
the new instance is <__main__.T1 object at 0x7f502c104290>
<__main__.T1 object at 0x7f502c104290>
>>> T2()
T2 (T2) was instantiated
the new instance is <__main__.T2 object at 0x7f502c0f7ed0>
<__main__.T2 object at 0x7f502c0f7ed0>
>>> T3()
T3 (T2) was instantiated
the new instance is <__main__.T3 object at 0x7f502c104290>
T3 (T3) was instantiated
the new instance is <__main__.T3 object at 0x7f502c104290>
<__main__.T3 object at 0x7f502c104290>

For 3: Yes, from __call__ as shown above.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜