Prenons l'exemple minimal suivant:

import abc

class FooClass(object):
  __metaclass__ = abc.ABCMeta

  @abc.abstractmethod
  def FooMethod(self):
    raise NotImplementedError()


def main():
  derived_type = type('Derived', (FooClass,), {})

  def BarOverride(self):
    print 'Hello, world!'
  derived_type.FooMethod = BarOverride

  instance = derived_type()

L'exécution de main() vous permet:

TypeError: Can't instantiate abstract class Derived with abstract methods FooMethod

(L'exception se produit sur la ligne instance = derived_type().)

Mais FooMethod ne doit pas être abstrait: je l'ai remplacé par BarOverride. Alors, pourquoi cela soulève-t-il des exceptions?

Avertissement: Oui, je pourrais utiliser la syntaxe explicite class et accomplir exactement la même chose. (Et encore mieux, je peux le faire fonctionner!) Mais il s'agit d'un cas de test minimal, et l'exemple le plus important est la création dynamique de classes. :-) Et je suis curieux de savoir pourquoi cela ne fonctionne pas.

Modifier: Et pour éviter l'autre non-réponse évidente: je ne veux pas passer BarOverride dans le troisième argument à type: dans l'exemple réel, {{X2 }} doit être lié derived_type. C'est plus facile à faire si je peux définir BarOverride après la création de derived_type. (Si je ne peux pas faire ça, alors pourquoi?)

12
Thanatos 11 nov. 2011 à 01:49

4 réponses

Meilleure réponse

Parce que les docs le disent:

L'ajout dynamique de méthodes abstraites à une classe ou la tentative de modification de l'état d'abstraction d'une méthode ou d'une classe une fois qu'elle est créée ne sont pas pris en charge. La méthode abstractmethod () n'affecte que les sous-classes dérivées en utilisant l'héritage régulier; les "sous-classes virtuelles" enregistrées avec la méthode register () de l'ABC ne sont pas affectées.

Une métaclasse n'est appelée que lorsqu'une classe est définie. Lorsque abstractmethod a marqué une classe comme abstraite, ce statut ne changera pas plus tard.

5
Jochen Ritzel 10 nov. 2011 à 22:01

Eh bien, si vous devez le faire de cette façon, vous pouvez simplement passer un dict factice {'FooMethod':None} comme troisième argument à taper. Cela permet à derived_type de satisfaire à l'exigence d'ABCMeta que toutes les méthodes abstraites soient remplacées. Plus tard, vous pourrez fournir le véritable FooMethod:

def main():
  derived_type = type('Derived', (FooClass,), {'FooMethod':None})
  def BarOverride(self):
    print 'Hello, world!'
  setattr(derived_type, 'FooMethod', BarOverride)
  instance = derived_type()
2
unutbu 10 nov. 2011 à 22:11

Je sais que ce sujet est vraiment ancien mais ... C'est vraiment une bonne question.

Cela ne fonctionne pas car abc ne peut rechercher des méthodes abstraites que lors de l'instanciation de types, c'est-à-dire lorsque type('Derived', (FooClass,), {}) est en cours d'exécution. Tout getattr effectué après cela n'est pas accessible depuis abc.

Donc, setattr ne fonctionnera pas, buuut ... Votre problème d'adressage du nom d'une classe qui n'a pas été précédemment déclarée ou définie semble résoluble:

J'ai écrit une petite métaclasse qui vous permet d'utiliser un espace réservé "clazz" pour accéder à n'importe quelle classe qui obtiendra éventuellement la méthode que vous écrivez en dehors d'une définition de classe.

De cette façon, vous n'obtiendrez plus TypeError de abc, car vous pouvez maintenant définir votre méthode AVANT d'instituer votre type, puis la transmettre à type à l'argument dict. Ensuite, abc le verra comme un remplacement de méthode approprié.

Aaand, avec la nouvelle métaclasse, vous pouvez faire référence à l'objet classe pendant cette méthode. Et c'est super, car maintenant vous pouvez utiliser super! = P Je suppose que vous étiez aussi inquiet à ce sujet ...

Regarde:

import abc
import inspect

clazz = type('clazz', (object,), {})()

def clazzRef(func_obj):
    func_obj.__hasclazzref__ = True
    return func_obj

class MetaClazzRef(type):
    """Makes the clazz placeholder work.

    Checks which of your functions or methods use the decorator clazzRef
    and swaps its global reference so that "clazz" resolves to the
    desired class, that is, the one where the method is set or defined.

    """
    methods = {}
    def __new__(mcs, name, bases, dict):
        ret = super(MetaClazzRef, mcs).__new__(mcs, name, bases, dict)
        for (k,f) in dict.items():
            if getattr(f, '__hasclazzref__', False):
                if inspect.ismethod(f):
                    f = f.im_func
                if inspect.isfunction(f):
                    for (var,value) in f.func_globals.items():
                        if value is clazz:
                            f.func_globals[var] = ret
        return ret

class MetaMix(abc.ABCMeta, MetaClazzRef):
    pass

class FooClass(object):
    __metaclass__ = MetaMix
    @abc.abstractmethod
    def FooMethod(self):
        print 'Ooops...'
        #raise NotImplementedError()


def main():
    @clazzRef
    def BarOverride(self):
        print "Hello, world! I'm a %s but this method is from class %s!" % (type(self), clazz)
        super(clazz, self).FooMethod() # Now I have SUPER!!!

    derived_type = type('Derived', (FooClass,), {'FooMethod': BarOverride})

    instance = derived_type()
    instance.FooMethod()

    class derivedDerived(derived_type):
        def FooMethod(self):
            print 'I inherit from derived.'
            super(derivedDerived,self).FooMethod()

    instance = derivedDerived()
    instance.FooMethod()

main()

La sortie est:

Hello, world! I'm a <class 'clazz.Derived'> but this method is from class <class 'clazz.Derived'>!
Ooops...
I inherit from derived.
Hello, world! I'm a <class 'clazz.derivedDerived'> but this method is from class <class 'clazz.Derived'>!
Ooops...
3
rbertoche 5 févr. 2015 à 20:11

Jochen a raison; les méthodes abstraites sont définies lors de la création de la classe et ne seront pas modifiées simplement parce que vous réaffectez un attribut.

Vous pouvez le supprimer manuellement de la liste des méthodes abstraites en faisant

DerivedType.__abstractmethods__ = frozenset()

Ou

DerivedType.__abstractmethods__ = frozenset(
        elem for elem in DerivedType.__abstractmethods__ if elem != 'FooMethod')

Ainsi que setattr, il ne pense donc pas que FooMethod soit abstrait.

3
agf 10 nov. 2011 à 22:05