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.
- When using both
__new__
and__init__
in a metaclass, their arguments must be defined the same? - What's most efficient way to define class
__init__
in a metaclass? - Is there any way to refer to class instance (normally self) in a metaclass?
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 likedef __new__(metacls, name, bases, dct)
and a
__init__
method likedef __init__(cls, name, bases, dct)
So the signatures are not exactly the same, but they differ only in the first argument.
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.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.
精彩评论