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