Comment créer des propriétés abstraites dans des classes abstraites Python

118

Dans le code suivant, je crée une classe abstraite de base Base. Je veux que toutes les classes qui héritent de Basepour fournir la namepropriété, j'ai donc fait de cette propriété un @abstractmethod.

Ensuite, j'ai créé une sous-classe de Base, appelée Base_1, qui est censée fournir des fonctionnalités, mais reste abstraite. Il n'y a pas de namepropriété dans Base_1, mais néanmoins python instaure un objet de cette classe sans erreur. Comment créer des propriétés abstraites?

from abc import ABCMeta, abstractmethod
class Base(object):
    __metaclass__ = ABCMeta
    def __init__(self, strDirConfig):
        self.strDirConfig = strDirConfig

    @abstractmethod
    def _doStuff(self, signals):
        pass

    @property    
    @abstractmethod
    def name(self):
        #this property will be supplied by the inheriting classes
        #individually
        pass


class Base_1(Base):
    __metaclass__ = ABCMeta
    # this class does not provide the name property, should raise an error
    def __init__(self, strDirConfig):
        super(Base_1, self).__init__(strDirConfig)

    def _doStuff(self, signals):
        print 'Base_1 does stuff'


class C(Base_1):
    @property
    def name(self):
        return 'class C'


if __name__ == '__main__':
    b1 = Base_1('abc')  
Boris Gorelik
la source
Gotcha: Si vous oubliez d'utiliser le décorateur @propertydans class C, namereviendra à une méthode.
kevinarpe

Réponses:

117

Depuis Python 3.3, un bogue a été corrigé, ce qui signifie que le property()décorateur est maintenant correctement identifié comme abstrait lorsqu'il est appliqué à une méthode abstraite.

Remarque: la commande compte, vous devez utiliser @propertyavant@abstractmethod

Python 3.3+: ( documentation python ):

class C(ABC):
    @property
    @abstractmethod
    def my_abstract_property(self):
        ...

Python 2: ( documentation python )

class C(ABC):
    @abstractproperty
    def my_abstract_property(self):
        ...
James
la source
1
@James Comment le rendre compatible avec python 2 et aussi?
himanshu219
@James en fait je voulais dire pour les deux, mais peu importe, j'ai posté une réponse basée sur votre solution
himanshu219
45

Jusqu'à Python 3.3 , vous ne pouvez pas imbriquer @abstractmethodet @property.

Utilisez @abstractpropertypour créer des propriétés abstraites ( docs ).

from abc import ABCMeta, abstractmethod, abstractproperty

class Base(object):
    # ...
    @abstractproperty
    def name(self):
        pass

Le code lève maintenant l'exception correcte:

Traceback (dernier appel le plus récent):
  Fichier "foo.py", ligne 36, dans 
    b1 = Base_1 ('abc')  
TypeError: impossible d'instancier la classe abstraite Base_1 avec le nom des méthodes abstraites
codeape
la source
42
en fait, cette réponse est fausse pour les pythons plus jeunes: depuis 3.3, @abstractpropertyest déconseillée au profit d'une combinaison comme OP.
mouton volant
10
À partir de la documentation 3.3: docs.python.org/3/library/abc.html#abc.abstractproperty
codeape
donc jusqu'au 3.3, justeraise NotImplementedError
Sławomir Lenart
pouvons-nous hériter de l'objet et toujours utiliser les annotations ABC? Cela fonctionnera-t-il comme indiqué dans l'exemple?
santhosh kumar le
3

Basé sur la réponse de James ci-dessus

def compatibleabstractproperty(func):

    if sys.version_info > (3, 3):             
        return property(abstractmethod(func))
    else:
        return abstractproperty(func)

et utilisez-le comme décorateur

@compatibleabstractproperty
def env(self):
    raise NotImplementedError()
himanshu219
la source