273 votes

Comment créer des propriétés abstraites dans les classes abstraites de python ?

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

J'ai ensuite créé une sous-classe de Base appelé Base_1 qui est censé fournir certaines fonctionnalités, tout en restant abstrait. Il n'y a pas de name propriété en Base_1 mais python instaure néanmoins un objet de cette classe sans erreur. Comment créer des propriétés abstraites ?

from abc import ABCMeta, abstractmethod

class Base(object):
# class Base(metaclass = ABCMeta): <- Python 3
    __metaclass__ = ABCMeta
    def __init__(self, str_dir_config):
        self.str_dir_config = str_dir_config

    @abstractmethod
    def _do_stuff(self, signals):
        pass

    @property    
    @abstractmethod
    def name(self):
        """This property will be supplied by the inheriting classes
        individually.
        """
        pass

class Base1(Base):
    __metaclass__ = ABCMeta
    """This class does not provide the name property and should
    raise an error.
    """
    def __init__(self, str_dir_config):
        super(Base1, self).__init__(str_dir_config)
        # super().__init__(str_dir_config) <- Python 3

    def _do_stuff(self, signals):
        print "Base_1 does stuff"
        # print("Base_1 does stuff") <- Python 3

class C(Base1):
    @property
    def name(self):
        return "class C"

if __name__ == "__main__":
    b1 = Base1("abc")

339voto

James Points 3068

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

Note : L'ordre est important, vous devez utiliser @property ci-dessus @abstractmethod

Python 3.3+ : ( docs python ):

from abc import ABC, abstractmethod

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

Python 2 : ( docs python )

from abc import ABC, abstractproperty

class C(ABC):
    @abstractproperty
    def my_abstract_property(self):
        ...

58voto

codeape Points 38576

Jusqu'à ce que Python 3.3 vous ne pouvez pas nicher @abstractmethod y @property .

Utilisation @abstractproperty pour créer des propriétés abstraites ( docs ).

from abc import ABCMeta, abstractmethod, abstractproperty

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

Le code soulève maintenant l'exception correcte :

Traceback (most recent call last):
  File "foo.py", line 36, in 
    b1 = Base\_1('abc')  
TypeError: Can't instantiate abstract class Base\_1 with abstract methods name

12voto

Par exemple, vous pouvez définir les fonctions abstraites getter, setter et deleter avec @abstractmethod y @property , @name.setter o @name.deleter en Person classe abstraite comme indiqué ci-dessous. * @abstractmethod doit être le décorateur le plus proche, sinon une erreur se produit :

from abc import ABC, abstractmethod

class Person(ABC):

    @property
    @abstractmethod # The innermost decorator
    def name(self): # Abstract getter
        pass

    @name.setter
    @abstractmethod # The innermost decorator
    def name(self, name): # Abstract setter
        pass

    @name.deleter
    @abstractmethod # The innermost decorator
    def name(self): # Abstract deleter
        pass

Ensuite, vous pouvez étendre Person classe abstraite con Student classe , surcharge les fonctions abstraites getter, setter et deleter de la fonction Student classe , instancié Student classe et appeler les fonctions getter, setter et deleter comme indiqué ci-dessous :

class Student(Person):

    def __init__(self, name):
        self._name = name

    @property
    def name(self): # Overrides abstract getter
        return self._name

    @name.setter
    def name(self, name): # Overrides abstract setter
        self._name = name

    @name.deleter
    def name(self): # Overrides abstract deleter 
        del self._name

obj = Student("John") # Instantiates "Student" class
print(obj.name) # Getter
obj.name = "Tom" # Setter
print(obj.name) # Getter
del obj.name # Deleter
print(hasattr(obj, "name"))

Sortie :

John
Tom
False

En fait, même si vous ne surchargez pas le setter et le deleter abstraits de la fonction Student classe et instancie Student classe comme indiqué ci-dessous :

class Student(Person): # Extends "Person" class

    def __init__(self, name):
        self._name = name

    @property
    def name(self): # Overrides only abstract getter
        return self._name

    # @name.setter
    # def name(self, name): # Overrides abstract setter
    #     self._name = name

    # @name.deleter
    # def name(self): # Overrides abstract deleter 
    #     del self._name

obj = Student("John") # Instantiates "Student" class
# ...

Aucune erreur ne se produit comme indiqué ci-dessous :

John
Tom
False

Mais si vous ne surchargez pas les fonctions abstraites getter, setter et deleter de la fonction Student classe et instancie Student classe comme indiqué ci-dessous :

class Student(Person): # Extends "Person" class

    def __init__(self, name):
        self._name = name

    # @property
    # def name(self): # Overrides only abstract getter
    #     return self._name

    # @name.setter
    # def name(self, name): # Overrides abstract setter
    #     self._name = name

    # @name.deleter
    # def name(self): # Overrides abstract deleter 
    #     del self._name

obj = Student("John") # Instantiates "Student" class
# ...

L'erreur ci-dessous se produit :

TypeError : Impossible d'instancier la classe abstraite Student avec le nom des méthodes abstraites

Et, si vous ne remplacez pas le getter abstrait de Student classe et instancie Student classe comme indiqué ci-dessous :

class Student(Person): # Extends "Person" class

    def __init__(self, name):
        self._name = name

    # @property
    # def name(self): # Overrides only abstract getter
    #     return self._name

    @name.setter
    def name(self, name): # Overrides abstract setter
        self._name = name

    @name.deleter
    def name(self): # Overrides abstract deleter 
        del self._name

obj = Student("John") # Instantiates "Student" class
# ...

L'erreur ci-dessous se produit :

NameError : le nom "name" n'est pas défini

Et si @abstractmethod n'est pas le décorateur le plus proche, comme indiqué ci-dessous :

from abc import ABC, abstractmethod

class Person(ABC):

    @abstractmethod # Not the innermost decorator
    @property
    def name(self): # Abstract getter
        pass

    @name.setter
    @abstractmethod # The innermost decorator
    def name(self, name): # Abstract setter
        pass

    @name.deleter
    @abstractmethod # The innermost decorator
    def name(self): # Abstract deleter
        pass

L'erreur ci-dessous se produit :

AttributeError : attribut ' Méthode abstraite ' des objets "propriété" n'est pas accessible en écriture

11voto

himanshu219 Points 443

Sur la base de 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 l'utiliser comme décorateur

@compatibleabstractproperty
def env(self):
    raise NotImplementedError()

7voto

Gers Points 63

En python 3.6+ Il est également possible d'anoter une variable sans fournir de valeur par défaut. Je trouve que c'est une façon plus concise de la rendre abstraite.

class Base():
    name: str

    def print_name(self):
        print(self.name)  # will raise an Attribute error at runtime if `name` isn't defined in subclass

class Base_1(Base):
    name = "base one"

il peut également être utilisé pour vous obliger à initialiser la variable dans le fichier __new__ o __init__ méthodes

Autre exemple, le code suivant échouera lorsque vous tenterez d'initialiser l'élément Base_1 classe

    class Base():
        name: str

        def __init__(self):
            self.print_name()

    class Base_1(Base):
        _nemo = "base one"

    b = Base_1() 

AttributeError: 'Base_1' object has no attribute 'name'

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X