web-dev-qa-db-fra.com

Est-il possible de faire des classes abstraites en Python?

Comment créer un résumé de classe ou de méthode en Python?

J'ai essayé de redéfinir __new__() comme ceci:

class F:
    def __new__(cls):
        raise Exception("Unable to create an instance of abstract class %s" %cls)

mais maintenant, si je crée une classe G qui hérite de F comme ceci:

class G(F):
    pass

alors je ne peux pas non plus instancier G, puisqu'il appelle la méthode __new__ de sa super-classe.

Existe-t-il un meilleur moyen de définir une classe abstraite?

247
user1632861

Utilisez le module abc pour créer des classes abstraites. Utilisez le décorateur abstractmethod pour déclarer un résumé de méthode et déclarer un résumé de classe de trois façons, selon votre version Python.

Dans Python 3.4 et versions ultérieures, vous pouvez hériter de ABC . Dans les versions antérieures de Python, vous devez spécifier la métaclasse de votre classe sous la forme ABCMeta . La spécification de la métaclasse a une syntaxe différente dans Python 3 et Python 2. Les trois possibilités sont présentées ci-dessous:

# Python 3.4+
from abc import ABC, abstractmethod
class Abstract(ABC):
    @abstractmethod
    def foo(self):
        pass
# Python 3.0+
from abc import ABCMeta, abstractmethod
class Abstract(metaclass=ABCMeta):
    @abstractmethod
    def foo(self):
        pass
# Python 2
from abc import ABCMeta, abstractmethod
class Abstract:
    __metaclass__ = ABCMeta

    @abstractmethod
    def foo(self):
        pass

Quelle que soit la méthode utilisée, vous ne pourrez pas instancier une classe abstraite dotée de méthodes abstraites, mais vous pourrez instancier une sous-classe fournissant des définitions concrètes de ces méthodes:

>>> Abstract()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Abstract with abstract methods foo
>>> class StillAbstract(Abstract):
...     pass
... 
>>> StillAbstract()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class StillAbstract with abstract methods foo
>>> class Concrete(Abstract):
...     def foo(self):
...         print('Hello, World')
... 
>>> Concrete()
<__main__.Concrete object at 0x7fc935d28898>
453
alexvassel

Pour ce faire, la méthode old-school (pre - PEP 3119 ) consiste simplement à raise NotImplementedError dans la classe abstraite lorsqu’une méthode abstraite est appelée.

class Abstract(object):
    def foo(self):
        raise NotImplementedError('subclasses must override foo()!')

class Derived(Abstract):
    def foo(self):
        print 'Hooray!'

>>> d = Derived()
>>> d.foo()
Hooray!
>>> a = Abstract()
>>> a.foo()
Traceback (most recent call last): [...]

Cela n’a pas les mêmes propriétés de Nice que le module abc. Vous pouvez toujours instancier la classe de base abstraite elle-même et vous ne trouverez pas votre erreur jusqu'à ce que vous appeliez la méthode abstraite au moment de l'exécution.

Mais si vous traitez avec un petit ensemble de classes simples, peut-être avec quelques méthodes abstraites, cette approche est un peu plus facile que d'essayer de parcourir la documentation abc.

85
Tim Gilbert

Voici un moyen très facile sans avoir à traiter avec le module ABC.

Dans la méthode __init__ de la classe pour laquelle vous voulez être une classe abstraite, vous pouvez vérifier le "type" de self. Si le type de self est la classe de base, alors l'appelant tente d'instancier la classe de base, alors déclenchez une exception. Voici un exemple simple:

class Base():
    def __init__(self):
        if type(self) is Base:
            raise Exception('Base is an abstract class and cannot be instantiated directly')
        # Any initialization code
        print('In the __init__  method of the Base class')

class Sub(Base):
    def __init__(self):
        print('In the __init__ method of the Sub class before calling __init__ of the Base class')
        super().__init__()
        print('In the __init__ method of the Sub class after calling __init__ of the Base class')

subObj = Sub()
baseObj = Base()

Lorsqu'il est exécuté, il produit:

In the `__init__` method of the Sub class before calling `__init__` of the Base class

In the `__init__`  method of the Base class

In the `__init__` method of the Sub class after calling `__init__` of the Base class
Traceback (most recent call last):

  File "/Users/irvkalb/Desktop/Demo files/Abstract.py", line 16, in <module>
    baseObj = Base()

  File "/Users/irvkalb/Desktop/Demo files/Abstract.py", line 4, in `__init__`

    raise Exception('Base is an abstract class and cannot be instantiated directly')

Exception: Base is an abstract class and cannot be instantiated directly

Cela montre que vous pouvez instancier une sous-classe qui hérite d'une classe de base, mais que vous ne pouvez pas instancier directement la classe de base.

Irv

11
Irv Kalb

La plupart des réponses précédentes étaient correctes mais voici la réponse et l'exemple pour Python 3.7. Oui, vous pouvez créer une classe et une méthode abstraites. Tout comme un rappel, une classe doit parfois définir une méthode qui appartient logiquement à une classe, mais cette classe ne peut pas spécifier comment implémenter la méthode. Par exemple, dans les cours Parents et Bébés ci-dessous, ils mangent tous les deux, mais la mise en œuvre sera différente pour chacun, car les bébés et les parents mangent un type de nourriture différent et le nombre de fois qu'ils le mangent est différent. Donc, les sous-classes de méthode eat remplacent AbstractClass.eat.

from abc import ABC, abstractmethod

class AbstractClass(ABC):

    def __init__(self, value):
        self.value = value
        super().__init__()

    @abstractmethod
    def eat(self):
        pass

class Parents(AbstractClass):
    def eat(self):
        return "eat solid food "+ str(self.value) + " times each day"

class Babies(AbstractClass):
    def eat(self):
        return "Milk only "+ str(self.value) + " times or more each day"

food = 3    
mom = Parents(food)
print("moms ----------")
print(mom.eat())

infant = Babies(food)
print("infants ----------")
print(infant.eat())

SORTIE:

moms ----------
eat solid food 3 times each day
infants ----------
Milk only 3 times or more each day
7
grepit

Celui-ci fonctionnera dans python 3

from abc import ABCMeta, abstractmethod

class Abstract(metaclass=ABCMeta):

    @abstractmethod
    def foo(self):
        pass

Abstract()
>>> TypeError: Can not instantiate abstract class Abstract with abstract methods foo
6
Rajkumar SR

Comme expliqué dans les autres réponses, vous pouvez utiliser des classes abstraites dans Python à l'aide du module abc . Ci-dessous, je donne un exemple réel en utilisant abstract @classmethod , @property et @abstractmethod (en utilisant Python 3.6+). Pour moi, il est généralement plus facile de commencer avec des exemples que je peux facilement copier et coller; J'espère que cette réponse est également utile pour les autres.

Commençons par créer une classe de base appelée Base:

from abc import ABC, abstractmethod

class Base(ABC):

    @classmethod
    @abstractmethod
    def from_dict(cls, d):
        pass

    @property
    @abstractmethod
    def prop1(self):
        pass

    @property
    @abstractmethod
    def prop2(self):
        pass

    @prop2.setter
    @abstractmethod
    def prop2(self, val):
        pass

    @abstractmethod
    def do_stuff(self):
        pass

Notre classe Base aura toujours un from_dictclassmethod, un propertyprop1 (en lecture seule) et un propertyprop2 (qui peut également être défini), ainsi qu'une fonction appelée do_stuff. Quelle que soit la classe construite à partir de Base, elle devra toutes les implémenter pour les méthodes/propriétés. Veuillez noter que pour le résumé classmethod et le résumé property, deux décorateurs sont requis.

Maintenant, nous pourrions créer une classe A comme ceci:

class A(Base):
    def __init__(self, name, val1, val2):
        self.name = name
        self.__val1 = val1
        self._val2 = val2

    @classmethod
    def from_dict(cls, d):
        name = d['name']
        val1 = d['val1']
        val2 = d['val2']

        return cls(name, val1, val2)

    @property
    def prop1(self):
        return self.__val1

    @property
    def prop2(self):
        return self._val2

    @prop2.setter
    def prop2(self, value):
        self._val2 = value

    def do_stuff(self):
        print('juhu!')

    def i_am_not_abstract(self):
        print('I can be customized')

Toutes les méthodes/propriétés requises sont implémentées et nous pouvons - bien sûr - également ajouter des fonctions supplémentaires qui ne font pas partie de Base (ici: i_am_not_abstract).

Maintenant nous pouvons faire:

a1 = A('dummy', 10, 'stuff')
a2 = A.from_dict({'name': 'from_d', 'val1': 20, 'val2': 'stuff'})

a1.prop1
# prints 10

a1.prop2
# prints 'stuff'

Comme vous le souhaitez, nous ne pouvons pas définir prop1:

a.prop1 = 100

reviendra

AttributeError: impossible de définir l'attribut

Notre méthode from_dict fonctionne aussi très bien:

a2.prop1
# prints 20

Si nous définissons maintenant une seconde classe B comme ceci:

class B(Base):
    def __init__(self, name):
        self.name = name

    @property
    def prop1(self):
        return self.name

et essayé d'instancier un objet comme celui-ci:

b = B('iwillfail')

nous aurons une erreur

TypeError: Impossible d'instancier la classe abstraite B avec les méthodes abstraites do_stuff, from_dict, prop2

listant toutes les choses définies dans Base que nous n'avons pas implémentées dans B.

1
Cleb

Oui, vous pouvez créer des classes abstraites dans python avec le module abc (abstract base classes).

Ce site vous aidera avec: http://docs.python.org/2/library/abc.html

1
ORION

Vous pouvez également exploiter la méthode __new__ à votre avantage. Tu as juste oublié quelque chose. La méthode __new__ renvoie toujours le nouvel objet. Vous devez donc renvoyer la nouvelle méthode de sa superclasse. Faites comme suit.

class F:
    def __new__(cls):
        if cls is F:
            raise TypeError("Cannot create an instance of abstract class '{}'".format(cls.__name__))
        return super().__new__(cls)

Lorsque vous utilisez la nouvelle méthode, vous devez renvoyer l'objet et non le mot clé None. C'est tout ce que tu as manqué.

0
Michael

cela fonctionne aussi et est simple:

class A_abstract(object):

    def __init__(self):
        # quite simple, old-school way.
        if self.__class__.__== "A_abstract": 
            raise NotImplementedError("You can't instantiate this abstract class. Derive it, please.")

class B(A_abstract):

        pass

b = B()

# here an exception is raised:
a = A_abstract()
0
Giovanni Angeli