Daniel Urban <[email protected]> added the comment:
> What also worries me is the difference between the "class"
> statement and the type() function.
I think the reason of this is that the class statement uses the __build_class__
builtin function. This function determines the metaclass to use (by getting the
metaclass of the first base class), and calls it. When one directly calls type,
one doesn't call the metaclass (though type.__new__ will later call the "real"
metaclass).
An example:
>>> class M_A(type):
... def __new__(mcls, name, bases, ns):
... print('M_A.__new__', mcls, name, bases)
... return super().__new__(mcls, name, bases, ns)
...
>>> class M_B(M_A):
... def __new__(mcls, name, bases, ns):
... print('M_B.__new__', mcls, name, bases)
... return super().__new__(mcls, name, bases, ns)
...
>>> class A(metaclass=M_A): pass
...
M_A.__new__ <class '__main__.M_A'> A ()
>>>
>>> class B(metaclass=M_B): pass
...
M_B.__new__ <class '__main__.M_B'> B ()
M_A.__new__ <class '__main__.M_B'> B ()
>>>
>>>
>>> class C(A, B): pass
...
M_A.__new__ <class '__main__.M_A'> C (<class '__main__.A'>, <class
'__main__.B'>)
M_B.__new__ <class '__main__.M_B'> C (<class '__main__.A'>, <class
'__main__.B'>)
M_A.__new__ <class '__main__.M_B'> C (<class '__main__.A'>, <class
'__main__.B'>)
>>>
Above __build_class__ calls M_A (because that is the metaclass of the first
base class, A). Then M_A calls type.__new__ with super(), then type.__new__
searches the "real" metaclass, M_B, and calls its __new__. Then M_B.__new__
calls again M_A.__new__.
>>> D = type('D', (A, B), {})
M_B.__new__ <class '__main__.M_B'> D (<class '__main__.A'>, <class
'__main__.B'>)
M_A.__new__ <class '__main__.M_B'> D (<class '__main__.A'>, <class
'__main__.B'>)
>>>
Above type.__call__ directly calls type.__new__, which determines the "real"
metaclass, M_B, and calls it (which then class M_A):
>>> class C2(B, A): pass
...
M_B.__new__ <class '__main__.M_B'> C2 (<class '__main__.B'>, <class
'__main__.A'>)
M_A.__new__ <class '__main__.M_B'> C2 (<class '__main__.B'>, <class
'__main__.A'>)
>>>
If we reverse the order of the base classes of C (as above for C2),
__build_class__ will use M_B as the metaclass.
>>> D2 = M_B('D', (A, B), {})
M_B.__new__ <class '__main__.M_B'> D (<class '__main__.A'>, <class
'__main__.B'>)
M_A.__new__ <class '__main__.M_B'> D (<class '__main__.A'>, <class
'__main__.B'>)
>>>
And of course, if we call directly the "real" metaclass, M_B (as above), we get
the same result.
I used the expression "real metaclass" with the meaning "the __class__ of the
class we are currently creating":
>>> C.__class__
<class '__main__.M_B'>
>>> C2.__class__
<class '__main__.M_B'>
>>> D.__class__
<class '__main__.M_B'>
>>> D2.__class__
<class '__main__.M_B'>
Summary: the problem seems to be, that __build_class__ doesn't call the "real"
metaclass, but the metaclass of the first base. (Note: I think this is
approximately consistent with the documentation: "Otherwise, if there is at
least one base class, its metaclass is used." But I don't know, if this is the
desired behaviour.)
This behaviour of __build_class__ can result in problems. For example, if the
two metaclasses define __prepare__. In some cases __build_class__ won't call
the "real" metaclass' __prepare__, but the other's:
>>> class M_A(type):
... def __new__(mcls, name, bases, ns):
... print('M_A.__new__', mcls, name, bases)
... return super().__new__(mcls, name, bases, ns)
... @classmethod
... def __prepare__(mcls, name, bases):
... print('M_A.__prepare__', mcls, name, bases)
... return {}
...
>>> class M_B(M_A):
... def __new__(mcls, name, bases, ns):
... print('M_B.__new__', mcls, name, bases, ns)
... return super().__new__(mcls, name, bases, ns)
... @classmethod
... def __prepare__(mcls, name, bases):
... print('M_B.__prepare__', mcls, name, bases)
... return {'M_B_was_here': True}
...
The __prepare__ method of the two metaclass differs, M_B leaves a
'M_B_was_here' name in the namespace.
>>> class A(metaclass=M_A): pass
...
M_A.__prepare__ <class '__main__.M_A'> A ()
M_A.__new__ <class '__main__.M_A'> A ()
>>>
>>> class B(metaclass=M_B): pass
...
M_B.__prepare__ <class '__main__.M_B'> B ()
M_B.__new__ <class '__main__.M_B'> B () {'M_B_was_here': True, '__module__':
'__main__'}
M_A.__new__ <class '__main__.M_B'> B ()
>>>
>>>
>>> class C(A, B): pass
...
M_A.__prepare__ <class '__main__.M_A'> C (<class '__main__.A'>, <class
'__main__.B'>)
M_A.__new__ <class '__main__.M_A'> C (<class '__main__.A'>, <class
'__main__.B'>)
M_B.__new__ <class '__main__.M_B'> C (<class '__main__.A'>, <class
'__main__.B'>) {'__module__': '__main__'}
M_A.__new__ <class '__main__.M_B'> C (<class '__main__.A'>, <class
'__main__.B'>)
>>>
>>> 'M_B_was_here' in C.__dict__
False
>>>
__build_class__ calls M_A.__prepare__, so the new class won't have a
'M_B_was_here' attribute (though its __class__ is M_B).
>>> class C2(B, A): pass
...
M_B.__prepare__ <class '__main__.M_B'> C2 (<class '__main__.B'>, <class
'__main__.A'>)
M_B.__new__ <class '__main__.M_B'> C2 (<class '__main__.B'>, <class
'__main__.A'>) {'M_B_was_here': True, '__module__': '__main__'}
M_A.__new__ <class '__main__.M_B'> C2 (<class '__main__.B'>, <class
'__main__.A'>)
>>>
>>> 'M_B_was_here' in C2.__dict__
True
>>>
I we reverse the order of the bases, M_B.__prepare__ is called.
>>> C.__class__
<class '__main__.M_B'>
>>> C2.__class__
<class '__main__.M_B'>
But the "real" metaclass of both classes is M_B.
(Sorry for the long post.)
----------
_______________________________________
Python tracker <[email protected]>
<http://bugs.python.org/issue1294232>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe:
http://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com