This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

Author daniel.urban
Recipients amaury.forgeotdarc, daniel.urban, georg.brandl, pwerneck, rodsenra, terry.reedy
Date 2010-12-05.12:15:56
SpamBayes Score 9.85349e-09
Marked as misclassified No
Message-id <1291551357.59.0.85718734705.issue1294232@psf.upfronthosting.co.za>
In-reply-to
Content
> 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.)
History
Date User Action Args
2010-12-05 12:15:57daniel.urbansetrecipients: + daniel.urban, georg.brandl, terry.reedy, amaury.forgeotdarc, rodsenra, pwerneck
2010-12-05 12:15:57daniel.urbansetmessageid: <1291551357.59.0.85718734705.issue1294232@psf.upfronthosting.co.za>
2010-12-05 12:15:56daniel.urbanlinkissue1294232 messages
2010-12-05 12:15:56daniel.urbancreate