J'ai une classe1 qui doit hériter de 2 métaclasses différentes qui sont Meta1 et abc.ABCMeta

Implémentation actuelle:

Implémentation de Meta1:

class Meta1(type):
    def __new__(cls, classname, parent, attr):
        new_class = type.__new__(cls, classname, parent, attr)
        return super(Meta1, cls).__new__(cls, classname, parent, attr)

Implémentation de class1Abstract

class class1Abstract(object):
    __metaclass__ = Meta1
    __metaclass__ = abc.ABCMeta

Mise en œuvre de la classe principale

class mainClass(class1Abstract):
    # do abstract method stuff

Je sais que c'est mal d'implémenter deux méta différentes deux fois.

Je change la façon dont la metclass est chargée (quelques essais) et j'obtiens ce TypeError: Erreur lors de l'appel des bases de métaclasse

metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

J'ai manqué d'idées ...


EDITED 1

J'ai essayé cette solution, cela fonctionne, mais la classe principale n'est pas une instance de class1Abstract

print issubclass(mainClass, class1Abstract) # true
print isinstance(mainClass, class1Abstract) # false

Implémentation de class1Abstract

class TestMeta(Meta1):
    pass


class AbcMeta(object):
    __metaclass__ = abc.ABCMeta
    pass


class CombineMeta(AbcMeta, TestMeta):
    pass


class class1Abstract(object):
    __metaclass__ = CombineMeta

    @abc.abstractmethod
    def do_shared_stuff(self):
        pass

    @abc.abstractmethod
    def test_method(self):
        ''' test method '''

Implémentation de mainClass

class mainClass(class1Abstract):
    def do_shared_stuff(self):
        print issubclass(mainClass, class1Abstract) # True
        print isinstance(mainClass, class1Abstract) # False

Puisque mainClass hérite d'une classe abstraite, python devrait se plaindre que test_method n'est pas implémenté dans mainClass. Mais il ne se plaint de rien car l'impression est une instance (mainClass, class1Abstract) # False

dir(mainClass) n'a pas

['__abstractmethods__', '_abc_cache', '_abc_negative_cache', '_abc_negative_cache_version', '_abc_registry']

AIDEZ-MOI!


EDITED 2

Implémentation de class1Abstract

CombineMeta = type("CombineMeta", (abc.ABCMeta, Meta1), {})
class class1Abstract(object):
    __metaclass__ = abc.ABCMeta

    @abc.abstractmethod
    def do_shared_stuff(self):
        pass

    @abc.abstractmethod
    def test_method(self):
        ''' test method '''

Implémentation de mainClass

class mainClass(class1Abstract):
    __metaclass__ = CombineMeta
    def do_shared_stuff(self):
        print issubclass(mainClass, class1Abstract) # True
        print isinstance(mainClass, class1Abstract) # False

dir(mainClass) dispose désormais des méthodes magiques de abstractmethod

['__abstractmethods__', '__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__metaclass__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_abc_cache', '_abc_negative_cache', '_abc_negative_cache_version', '_abc_registry', 'do_shared_stuff', 'test_method']

Mais python ne prévient pas que la méthode de test n'est pas instanciée

AIDEZ-MOI!

21
momokjaaaaa 13 juil. 2015 à 12:15

4 réponses

Meilleure réponse

Par défaut, python se plaint uniquement que la classe a des méthodes abstraites lorsque vous essayez d'instancier la classe, pas lorsque vous créez la classe. En effet, la métaclasse de la classe est toujours ABCMeta (ou son sous-type), il est donc autorisé d'avoir des méthodes abstraites.

Pour obtenir ce que vous voulez, vous devez écrire votre propre métaclasse qui génère une erreur lorsqu'elle détecte que __abstractmethods__ n'est pas vide. De cette façon, vous devez indiquer explicitement quand une classe n'est plus autorisée aux méthodes abstraites.

from abc import ABCMeta, abstractmethod

class YourMeta(type):
    def __init__(self, *args, **kwargs):
        super(YourMeta, self).__init__(*args, **kwargs)
        print "YourMeta.__init__"
    def __new__(cls, *args, **kwargs):
        newcls = super(YourMeta, cls).__new__(cls, *args, **kwargs)
        print "YourMeta.__new__"
        return newcls

class ConcreteClassMeta(ABCMeta):
    def __init__(self, *args, **kwargs):
        super(ConcreteClassMeta, self).__init__(*args, **kwargs)
        if self.__abstractmethods__:
            raise TypeError("{} has not implemented abstract methods {}".format(
                self.__name__, ", ".join(self.__abstractmethods__)))

class CombinedMeta(ConcreteClassMeta, YourMeta):
    pass

class AbstractBase(object):
    __metaclass__ = ABCMeta

    @abstractmethod
    def f(self):
        raise NotImplemented

try:
    class ConcreteClass(AbstractBase):
        __metaclass__ = CombinedMeta

except TypeError as e:
    print "Couldn't create class --", e

class ConcreteClass(AbstractBase):
    __metaclass__ = CombinedMeta
    def f(self):
        print "ConcreteClass.f"

assert hasattr(ConcreteClass, "__abstractmethods__")
c = ConcreteClass()
c.f()

Quelles sorties:

YourMeta.__new__
YourMeta.__init__
Couldn't create class -- ConcreteClass has not implemented abstract methods f
YourMeta.__new__
YourMeta.__init__
ConcreteClass.f
10
Dunes 15 juil. 2015 à 11:31

Dans votre code édité (1 et 2), vous avez presque terminé. La seule mauvaise chose est la façon dont vous utilisez isinstance. Vous voulez vérifier si une instance de classe (dans ce cas self) est une instance d'une classe donnée (class1Abstract). Par exemple:

class mainClass(class1Abstract):
def do_shared_stuff(self):
    print issubclass(mainClass, class1Abstract) # True
    print isinstance(self, class1Abstract) # True
0
islijepcevic 21 juil. 2015 à 22:15

Pas besoin de définir deux métaclasses: Meta1 devrait hériter de abc.ABCMeta.

0
Daniel Roseman 13 juil. 2015 à 09:28

En Python, chaque classe ne peut avoir qu'une seule métaclasse, pas plusieurs. Cependant, il est possible d'obtenir un comportement similaire (comme s'il avait plusieurs métaclasses) en mélangeant ce que ces métaclasses font .

Commençons simple. Notre propre métaclasse, ajoute simplement un nouvel attribut à une classe:

class SampleMetaClass(type):
  """Sample metaclass: adds `sample` attribute to the class"""
  def __new__(cls, clsname, bases, dct):
    dct['sample'] = 'this a sample class attribute'
    return super(SampleMetaClass, cls).__new__(cls, clsname, bases, dct)

class MyClass(object):
  __metaclass__ = SampleMetaClass

print("SampleMetaClass was mixed in!" if 'sample' in MyClass.__dict__ else "We've had a problem here")

Cela imprime "SampleMetaClass a été mélangé!", Nous savons donc que notre métaclasse de base fonctionne très bien.

Maintenant, de l'autre côté, nous voulons une classe abstraite, dans sa forme la plus simple ce serait:

from abc import ABCMeta, abstractmethod

class AbstractClass(object):
  __metaclass__ = ABCMeta
  @abstractmethod
  def implement_me(self):
    pass

class IncompleteImplementor(AbstractClass):
  pass

class MainClass(AbstractClass):
  def implement_me(self):
    return "correct implementation in `MainClass`"

try:
  IncompleteImplementor()
except TypeError as terr:
  print("missing implementation in `IncompleteImplementor`")

MainClass().implement_me()

Ceci affiche "l'implémentation manquante dans IncompleteImplementor" suivie par "l'implémentation correcte dans MainClass". Ainsi, la classe abstraite fonctionne également très bien.

Maintenant, nous avons 2 implémentations simples et nous devons mélanger le comportement des deux métaclasses. Il existe plusieurs options ici.

Option 1 - sous-classement

On peut implémenter un SampleMetaClass en tant que sous-classe de ABCMeta - les métaclasses sont également des classes et on peut les en inhérence!

class SampleMetaABC(ABCMeta):
  """Same as SampleMetaClass, but also inherits ABCMeta behaviour"""
  def __new__(cls, clsname, bases, dct):
    dct['sample'] = 'this a sample class attribute'
    return super(SampleMetaABC, cls).__new__(cls, clsname, bases, dct)

Maintenant, nous changeons la métaclasse dans la définition AbstractClass:

class AbstractClass(object):
  __metaclass__ = SampleMetaABC
  @abstractmethod
  def implement_me(self):
    pass

# IncompleteImplementor and MainClass implementation is the same, but make sure to redeclare them if you use same interpreter from the previous test

Et exécutez à nouveau nos deux tests:

try:
  IncompleteImplementor()
except TypeError as terr:
  print("missing implementation in `IncompleteImplementor`")

MainClass().implement_me()

print("sample was added!" if 'sample' in IncompleteImplementor.__dict__ else "We've had a problem here")
print("sample was added!" if 'sample' in MainClass.__dict__ else "We've had a problem here")

Cela afficherait toujours que IncompleteImplementor n'est pas correctement implémenté, que MainClass l'est et que les deux ont désormais l'attribut de niveau de classe sample ajouté. À noter ici que Sample une partie de la métaclasse a également été appliquée avec succès à IncompleteImplementor (eh bien, il n'y a aucune raison que ce ne soit pas le cas).

Comme on pouvait s'y attendre, isinstance et issubclass fonctionnent toujours comme supposés:

print(issubclass(MainClass, AbstractClass)) # True, inheriting from AbtractClass
print(isinstance(MainClass, AbstractClass)) # False as expected - AbstractClass is a base class, not a metaclass
print(isinstance(MainClass(), AbstractClass)) # True, now created an instance here

Option 2 - composer des métaclasses

En fait, il y a cette option dans la question elle-même, elle ne nécessitait qu'une petite correction. Déclarez une nouvelle métaclasse comme une composition de plusieurs métaclasses plus simples pour mélanger leur comportement:

SampleMetaWithAbcMixin = type('SampleMetaWithAbcMixin', (ABCMeta, SampleMetaClass), {})

Comme précédemment, modifiez la métaclasse pour un AbstractClass (et encore une fois, IncompleteImplementor et MainClass ne changent pas, mais redéclarez-les si dans le même interpréteur):

class AbstractClass(object):
  __metaclass__ = SampleMetaWithAbcMixin
  @abstractmethod
  def implement_me(self):
    pass

À partir de là, l'exécution des mêmes tests devrait donner les mêmes résultats: ABCMeta fonctionne toujours et garantit que @abstractmethod - s sont implémentés, SampleMetaClass ajoute un attribut sample.

Personnellement, je préfère cette dernière option, pour la même raison que je préfère généralement la composition à l'héritage: plus il faut de combinaisons entre plusieurs classes (méta) - plus ce serait simple avec la composition.

En savoir plus sur les métaclasses

Enfin, la meilleure explication des métaclasses que j'ai jamais lue est cette réponse SO: Qu'est-ce qu'une métaclasse en Python?

13
Community 23 mai 2017 à 11:46