Est-il possible de créer des classes abstraites en Python?

321

Comment puis-je créer une classe ou une méthode abstraite en Python?

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

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 Gqui hérite de Fcomme ça:

class G(F):
    pass

alors je ne peux pas non plus instancier G, car il appelle la __new__méthode de sa super classe .

Existe-t-il une meilleure façon de définir une classe abstraite?

Martin Thoma
la source
Oui, vous pouvez créer des classes abstraites en python avec le module abc (classes de base abstraites). Ce site vous y aidera: http://docs.python.org/2/library/abc.html
ORION

Réponses:

554

Utilisez le abcmodule pour créer des classes abstraites. Utilisez le abstractmethoddécorateur pour déclarer un résumé de méthode, et déclarez un résumé de classe en utilisant l'une des trois façons, selon votre version Python.

Dans Python 3.4 et supérieur, vous pouvez hériter de ABC. Dans les versions antérieures de Python, vous devez spécifier la métaclasse de votre classe en tant que 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 façon dont vous utilisez, vous ne pourrez pas instancier une classe abstraite qui a des méthodes abstraites, mais pourrez instancier une sous-classe qui fournit 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>
alexvassel
la source
9
que fait la @abstractmethod? Pourquoi en avez-vous besoin? Si la classe est déjà établie comme abstraite, le compilateur / interprète ne devrait-il pas savoir que toutes les méthodes proviennent de la classe abstraite en question?
Charlie Parker
30
@CharlieParker - Le @abstractmethodfait en sorte que la fonction décorée doit être remplacée avant que la classe puisse être instanciée. De la documentation:A class that has a metaclass derived from ABCMeta cannot be instantiated unless all of its abstract methods and properties are overridden.
Fake Name
6
@CharlieParker - Fondamentalement, il vous permet de définir une classe d'une manière où la sous-classe doit implémenter une suite spécifiée de méthodes pour instancier du tout.
Fake Name
13
Existe-t-il un moyen d'interdire aux utilisateurs de créer Abstract () sans aucune méthode @abstractmethod?
Joe
2
@ Joe il semble que vous pouvez utiliser @abstractmethodpour la __init__méthode aussi bien, voir stackoverflow.com/q/44800659/547270
scrutari
108

La méthode old-school (pré- PEP 3119 ) consiste à utiliser raise NotImplementedErrorla 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 intéressantes que l'utilisation du abcmodule. Vous pouvez toujours instancier la classe de base abstraite elle-même, et vous ne trouverez votre erreur que lorsque vous appellerez la méthode abstraite au moment de l'exécution.

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

Tim Gilbert
la source
1
Appréciez la simplicité et l'efficacité de cette approche.
mohit6up
Haha, j'ai vu ça partout dans mon cours OMSCS et je n'avais aucune idée de ce que c'était :)
mLstudent33
18

Voici un moyen très simple sans avoir à gérer le module ABC.

Dans la __init__méthode de la classe que vous voulez être une classe abstraite, vous pouvez vérifier le "type" de soi. Si le type de self est la classe de base, alors l'appelant essaie d'instancier la classe de base, alors lève 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 vous ne pouvez pas instancier directement la classe de base.

Irv Kalb
la source
11

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. À titre de 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 classes 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 mangent est différent. Ainsi, 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())

PRODUCTION:

moms ----------
eat solid food 3 times each day
infants ----------
Milk only 3 times or more each day
grepit
la source
Peut import abc- être est inutile.
Benyamin Jafari
Pouvons-nous également écrire @abstractmethod sur la fonction init ?
variable
+1 Pourquoi @abstractmethod def eat (self)? Si cette classe est abstraite et ne doit donc pas être instanciée, pourquoi passez-vous (vous-même) à manger? Cela fonctionne très bien sans cela
VMMF
7

Celui-ci fonctionnera en 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
Rajkumar SR
la source
4

Comme expliqué dans les autres réponses, oui, vous pouvez utiliser des classes abstraites en Python en utilisant le abcmodule . Ci - dessous je donne un exemple réel à l' aide abstraite @classmethod, @propertyet @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 d'autres.

Créons d'abord 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 Baseclasse aura toujours un from_dict classmethod, un property prop1(qui est en lecture seule) et un property prop2(qui peut également être défini) ainsi qu'une fonction appelée do_stuff. Quelle que soit la classe qui est maintenant construite, elle Basedevra implémenter tout cela pour les méthodes / propriétés. Veuillez noter que pour qu'une méthode soit abstraite, deux décorateurs sont requis - classmethodet abstraits property.

Maintenant, nous pourrions créer une classe Acomme celle-ci:

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 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 souhaité, nous ne pouvons pas définir prop1:

a.prop1 = 100

reviendra

AttributeError: impossible de définir l'attribut

Notre from_dictméthode fonctionne également très bien:

a2.prop1
# prints 20

Si nous définissons maintenant une deuxième classe Bcomme celle-ci:

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

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

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

b = B('iwillfail')

nous obtiendrons une erreur

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

répertoriant toutes les choses définies dans Baselesquelles nous n'avons pas implémenté B.

Cleb
la source
2

cela fonctionne aussi et est simple:

class A_abstract(object):

    def __init__(self):
        # quite simple, old-school way.
        if self.__class__.__name__ == "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()
Giovanni Angeli
la source
2

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. Procédez 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, pas le mot clé None. C'est tout ce que tu as manqué.

Michael
la source
2

Je trouve la réponse acceptée, et toutes les autres étranges, puisqu'elles passent selfen classe abstraite. Une classe abstraite n'est pas instanciée et ne peut donc pas avoir de self.

Alors essayez ça, ça marche.

from abc import ABCMeta, abstractmethod


class Abstract(metaclass=ABCMeta):
    @staticmethod
    @abstractmethod
    def foo():
        """An abstract method. No need to write pass"""


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


FOO = Derived()
FOO.foo()
Sean Bradley
la source
1
Même la documentation abc utilise self dans son lien d'
igor Smirnov
2
 from abc import ABCMeta, abstractmethod

 #Abstract class and abstract method declaration
 class Jungle(metaclass=ABCMeta):
     #constructor with default values
     def __init__(self, name="Unknown"):
     self.visitorName = name

     def welcomeMessage(self):
         print("Hello %s , Welcome to the Jungle" % self.visitorName)

     # abstract method is compulsory to defined in child-class
     @abstractmethod
     def scarySound(self):
         pass
Shivam Bharadwaj
la source
Nice @Shivam Bharadwaj, je l'ai fait de la même manière
Muhammad Irfan
-3

Dans votre extrait de code, vous pouvez également résoudre ce problème en fournissant une implémentation de la __new__méthode dans la sous-classe, de la même manière:

def G(F):
    def __new__(cls):
        # do something here

Mais c'est un hack et je vous déconseille, à moins que vous sachiez ce que vous faites. Pour presque tous les cas, je vous conseille d'utiliser le abcmodule, que d'autres avant moi ont suggéré.

De même , lorsque vous créez une nouvelle classe, faire la sous - classe (base) object, comme ceci: class MyBaseClass(object):. Je ne sais plus si c'est beaucoup plus important, mais cela aide à conserver la cohérence du style sur votre code

NlightNFotis
la source
-4

Juste un ajout rapide à la réponse old-school de @ TimGilbert ... vous pouvez faire en sorte que la méthode init () de votre classe de base abstraite lève une exception et cela l'empêcherait d'être instanciée, non?

>>> class Abstract(object):
...     def __init__(self):
...         raise NotImplementedError("You can't instantiate this class!")
...
>>> a = Abstract()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __init__
NotImplementedError: You can't instantiate this class! 
Dave Wade-Stein
la source
6
Cela empêchera les sous-classes de bénéficier de tout code d' initialisation commun qui devrait logiquement être présent dans leur classe de base commune.
Arpit Singh
C'est suffisant. Voilà pour la vieille école.
Dave Wade-Stein,