web-dev-qa-db-fra.com

Incohérence d'héritage multiple de la métaclasse

Pourquoi est-ce:

class MyType(type):
    def __init__(cls, name, bases, attrs):
        print 'created', cls
class MyMixin:
    __metaclass__ = MyType
class MyList(list, MyMixin): pass

d'accord, et fonctionne comme prévu:

created <class '__main__.MyMixin'>
created <class '__main__.MyList'>

Mais ça:

class MyType(type):
    def __init__(cls, name, bases, attrs):
        print 'created', cls
class MyMixin:
    __metaclass__ = MyType
class MyObject(object, MyMixin): pass

N'est-ce pas bien, et explose ainsi?:

created <class '__main__.MyMixin'>
Traceback (most recent call last):
  File "/tmp/junk.py", line 11, in <module>
    class MyObject(object, MyMixin): pass
TypeError: Error when calling the metaclass bases
    Cannot create a consistent method resolution
order (MRO) for bases object, MyMixin
57
Matt Anderson

Ce n'est pas un problème de métaclasse personnalisée (bien que ce soit diagnostiqué au stade de la métaclasse):

>>> class Normal(object): pass
... 
>>> class MyObject(object, Normal): pass
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Error when calling the metaclass bases
    Cannot create a consistent method resolution
order (MRO) for bases object, Normal

et le problème est le même que celui-ci:

>>> class Derived(Normal): pass
... 
>>> class Ok(Derived, Normal): pass
... 
>>> class Nope(Normal, Derived): pass
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Error when calling the metaclass bases
    Cannot create a consistent method resolution
order (MRO) for bases Normal, Derived

c'est-à-dire, ne peut pas multiplier l'héritage d'une classe de base suivie d'une classe dérivée - il est impossible de définir un MRO cohérent qui satisfait les contraintes/garanties MRO habituelles.

Heureusement, vous ne voulez faire cela - la sous-classe remplace vraisemblablement une méthode de la classe de base (c'est ce que les sous-classes normales faire ;-), et ayant la base la classe "devant" signifierait "masquer le remplacement".

Mettre la classe de base après la classe dérivée est assez inutile, mais au moins c'est inoffensif (et cohérent avec les garanties MRO normales).

Votre premier exemple fonctionne bien sûr parce que MyMixin est pas dérivé de list:

>>> MyMixin.__mro__
(<class '__main__.MyMixin'>, <type 'object'>)

... mais il est dérivé de object (comme toute classe de style moderne Python)), donc le deuxième exemple ne peut pas fonctionner (tout à fait indépendamment de MyMixin ayant une métaclasse personnalisée).

83
Alex Martelli