Daniel Urban <urban.dani...@gmail.com> 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 <rep...@bugs.python.org>
<http://bugs.python.org/issue1294232>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
http://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to