New submission from Eldar Abusalimov:

When a new class is constructed Python checks for possible metaclass conflicts 
within bases and an explicitly specified one, if any, choosing the best 
available (the most specialized) one. That is the following implication is 
expected:

    issubclass(B, A) => issubclass(type(B), type(A))

However, changing __bases__ attribute can break this invariant silently without 
an error.

>>> class O(object):
...     pass
... 
>>> class M(type):
...     pass
... 
>>> class N(type):
...     pass
... 
>>> class A(O, metaclass=M):
...     pass
... 
>>> class B(O, metaclass=N):
...     pass
... 
>>> B.__bases__ = (A,)
>>> B.__mro__
(<class '__main__.B'>, <class '__main__.A'>, <class '__main__.O'>, <class 
'object'>)
>>> type(B)
<class '__main__.N'>
>>> type(A)
<class '__main__.M'>
>>> issubclass(B, A)
True
>>> issubclass(type(B), type(A))
False

Trying to derive from B now makes things look pretty weird:

>>> class C(A, metaclass=N):
...     pass
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: metaclass conflict: the metaclass of a derived class must be a 
(non-strict) subclass of the metaclasses of all its bases
>>> class D(B, A): pass
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: metaclass conflict: the metaclass of a derived class must be a 
(non-strict) subclass of the metaclasses of all its bases
>>> class E(B, metaclass=N):
...     pass
... 
>>> type(E)
<class '__main__.N'>

That is one can extend a class but not its base (and not a class along its 
base). This effectively allows to bypass metaclass checks (by introducing a 
dummy class with the default metaclass, deriving it from a desired class with 
an inappropriate metaclass by changing __bases__ and using it instead of the 
desired class).

This behavior is observed in 2.7, 3.2 and 3.4.


I would expect the same check for metaclass conflicts when changing __bases__ 
as upon creating a new class:

>>> # EXPECTED:
... 
>>> B.__bases__ = (A,)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: metaclass conflict: the metaclass of a derived class must be a 
(non-strict) subclass of the metaclasses of all its bases

----------
components: Interpreter Core
messages: 222312
nosy: Eldar.Abusalimov
priority: normal
severity: normal
status: open
title: Changing cls.__bases__ must ensure proper metaclass inheritance
type: behavior
versions: Python 2.7, Python 3.2, Python 3.4

_______________________________________
Python tracker <rep...@bugs.python.org>
<http://bugs.python.org/issue21919>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to