Ist es möglich, abstrakte Klassen in Python zu erstellen?

320

Wie kann ich eine Klasse oder Methode in Python abstrakt machen?

Ich habe versucht, __new__()so neu zu definieren:

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

aber jetzt, wenn ich eine Klasse erstelle G, die von Fso erbt :

class G(F):
    pass

dann kann ich auch nicht instanziieren G, da es die __new__Methode seiner Superklasse aufruft .

Gibt es eine bessere Möglichkeit, eine abstrakte Klasse zu definieren?

Martin Thoma
quelle
Ja, Sie können abstrakte Klassen in Python mit dem Modul abc (abstrakte Basisklassen) erstellen. Diese Seite wird Ihnen dabei helfen: http://docs.python.org/2/library/abc.html
ORION

Antworten:

550

Verwenden Sie das abcModul, um abstrakte Klassen zu erstellen. Verwenden Sie den abstractmethodDecorator, um eine Methodenzusammenfassung zu deklarieren, und deklarieren Sie eine Klassenzusammenfassung auf drei Arten, abhängig von Ihrer Python-Version.

In Python 3.4 und höher können Sie von erben ABC. In früheren Versionen von Python müssen Sie die Metaklasse Ihrer Klasse als angeben ABCMeta. Die Angabe der Metaklasse hat in Python 3 und Python 2 unterschiedliche Syntax. Die drei Möglichkeiten sind nachstehend aufgeführt:

# 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

Unabhängig davon, wie Sie es verwenden, können Sie keine abstrakte Klasse mit abstrakten Methoden instanziieren, sondern eine Unterklasse, die konkrete Definitionen dieser Methoden enthält:

>>> 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
quelle
9
Was macht die @abstractmethod? Wieso brauchst du es? Wenn die Klasse bereits als abstrakt eingerichtet ist, sollte der Compiler / Interpret dann nicht wissen, dass alle Methoden aus der betreffenden abstrakten Klasse stammen?
Charlie Parker
29
@CharlieParker - Das @abstractmethodmacht es so, dass die dekorierte Funktion überschrieben werden muss , bevor die Klasse instanziiert werden kann. Aus den Dokumenten:A class that has a metaclass derived from ABCMeta cannot be instantiated unless all of its abstract methods and properties are overridden.
Gefälschter Name
6
@CharlieParker - Grundsätzlich können Sie eine Klasse so definieren, dass die Unterklasse eine bestimmte Suite von Methoden implementieren muss, um überhaupt zu instanziieren.
Gefälschter Name
13
Gibt es eine Möglichkeit, Benutzern das Erstellen von Abstract () ohne @ abstractmethod-Methoden zu verbieten?
Joe
2
@ Joe scheint es , Sie verwenden können , @abstractmethodfür __init__Verfahren und finden stackoverflow.com/q/44800659/547270
scrutari
107

Die Methode der alten Schule (vor PEP 3119 ) besteht darin, nur raise NotImplementedErrorin der abstrakten Klasse zu arbeiten, wenn eine abstrakte Methode aufgerufen wird.

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): [...]

Dies hat nicht die gleichen schönen Eigenschaften wie die Verwendung des abcModuls. Sie können die abstrakte Basisklasse selbst weiterhin instanziieren, und Sie werden Ihren Fehler erst finden, wenn Sie die abstrakte Methode zur Laufzeit aufrufen.

Wenn Sie sich jedoch mit einer kleinen Anzahl einfacher Klassen beschäftigen, möglicherweise mit nur wenigen abstrakten Methoden, ist dieser Ansatz etwas einfacher als der Versuch, die abcDokumentation durchzuarbeiten .

Tim Gilbert
quelle
1
Schätzen Sie die Einfachheit und Effektivität dieses Ansatzes.
Mohit6up
Haha, ich habe das überall in meinem OMSCS-Kurs gesehen und hatte keine Ahnung, was es war :)
mLstudent33
18

Hier ist ein sehr einfacher Weg, ohne sich mit dem ABC-Modul befassen zu müssen.

In der __init__Methode der Klasse, die Sie eine abstrakte Klasse sein möchten, können Sie den "Typ" von self überprüfen. Wenn der Typ von self die Basisklasse ist, versucht der Aufrufer, die Basisklasse zu instanziieren, also eine Ausnahme auslösen. Hier ist ein einfaches Beispiel:

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()

Beim Ausführen wird Folgendes erzeugt:

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

Dies zeigt, dass Sie eine Unterklasse instanziieren können, die von einer Basisklasse erbt, die Basisklasse jedoch nicht direkt instanziieren können.

Irv Kalb
quelle
11

Die meisten vorherigen Antworten waren korrekt, aber hier ist die Antwort und das Beispiel für Python 3.7. Ja, Sie können eine abstrakte Klasse und Methode erstellen. Nur zur Erinnerung, manchmal sollte eine Klasse eine Methode definieren, die logisch zu einer Klasse gehört, aber diese Klasse kann nicht angeben, wie die Methode implementiert werden soll. In den folgenden Klassen für Eltern und Babys essen sie beide, aber die Implementierung ist für jede Klasse unterschiedlich, da Babys und Eltern eine andere Art von Essen essen und die Häufigkeit, mit der sie essen, unterschiedlich ist. Die Unterklassen der eat-Methode überschreiben also 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())

AUSGABE:

moms ----------
eat solid food 3 times each day
infants ----------
Milk only 3 times or more each day
grepit
quelle
Vielleicht import abcist nutzlos.
Benyamin Jafari
Können wir @abstractmethod auch auf die init- Funktion schreiben ?
Variable
+1 Warum @abstractmethod def eat (self)? Wenn diese Klasse abstrakt ist und daher nicht instanziiert werden soll, warum gehst du dann (selbst) zum Essen? Es funktioniert gut ohne es
VMMF
7

Dieser wird in Python 3 funktionieren

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
quelle
4

Wie in den anderen Antworten erläutert, können Sie mit dem abcModul abstrakte Klassen in Python verwenden . Im Folgenden gebe ich ein tatsächliches Beispiel mit abstraktem @classmethod, @propertyund @abstractmethod(mit Python 3.6 oder höher). Für mich ist es normalerweise einfacher, mit Beispielen zu beginnen, die ich einfach kopieren und einfügen kann. Ich hoffe, diese Antwort ist auch für andere nützlich.

Erstellen wir zunächst eine Basisklasse mit dem Namen 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

Unsere BaseKlasse wird immer ein from_dict classmethod, ein property prop1(schreibgeschützt) und ein property prop2(das auch gesetzt werden kann) sowie eine aufgerufene Funktion haben do_stuff. Unabhängig davon, auf welcher Klasse jetzt basiert Base, muss all dies für Methoden / Eigenschaften implementiert werden. Bitte beachten Sie, dass für eine abstrakte Methode zwei Dekorateure erforderlich sind - classmethodund eine abstrakte property.

Jetzt könnten wir eine Klasse Awie diese erstellen :

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')

Alle erforderlichen Methoden / Eigenschaften sind implementiert und wir können natürlich auch zusätzliche Funktionen hinzufügen, die nicht Teil von Base(hier :) sind i_am_not_abstract.

Jetzt können wir tun:

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

a1.prop1
# prints 10

a1.prop2
# prints 'stuff'

Wie gewünscht können wir nicht einstellen prop1:

a.prop1 = 100

wird zurückkehren

AttributeError: Attribut kann nicht festgelegt werden

Auch unsere from_dictMethode funktioniert gut:

a2.prop1
# prints 20

Wenn wir jetzt eine zweite Klasse Bwie diese definieren:

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

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

und versuchte, ein Objekt wie folgt zu instanziieren:

b = B('iwillfail')

Wir werden einen Fehler bekommen

TypeError: Abstrakte Klasse B kann nicht mit abstrakten Methoden do_stuff, from_dict, prop2 instanziiert werden

Auflisten aller definierten Dinge, in Basedenen wir nicht implementiert haben B.

Cleb
quelle
2

auch das funktioniert und ist einfach:

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
quelle
2

Sie können die __new__- Methode auch zu Ihrem Vorteil nutzen. Du hast gerade etwas vergessen. Die Methode __new__ gibt immer das neue Objekt zurück, daher müssen Sie die neue Methode der Oberklasse zurückgeben. Gehen Sie wie folgt vor.

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)

Wenn Sie die neue Methode verwenden, müssen Sie das Objekt zurückgeben, nicht das Schlüsselwort None. Das ist alles was du verpasst hast.

Michael
quelle
2

Ich finde die akzeptierte Antwort und alle anderen seltsam, da sie selfzu einer abstrakten Klasse übergehen . Eine abstrakte Klasse wird nicht instanziiert, kann also keine haben self.

Probieren Sie es aus, es funktioniert.

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
quelle
1
Auch abc Verwendung Dokumentation selbst in ihren Beispielen verknüpfen
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
quelle
Nizza @ Shivam Bharadwaj, ich habe es genauso gemacht
Muhammad Irfan
-3

In Ihrem Code-Snippet können Sie dies auch beheben, indem Sie eine Implementierung für die __new__Methode in der Unterklasse bereitstellen :

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

Aber das ist ein Hack und ich rate Ihnen davon ab, es sei denn, Sie wissen, was Sie tun. In fast allen Fällen empfehle ich Ihnen, das abcModul zu verwenden, das andere vor mir vorgeschlagen haben.

Wenn Sie eine neue (Basis-) Klasse erstellen, machen Sie sie objectwie folgt zu einer Unterklasse : class MyBaseClass(object):. Ich weiß nicht mehr, ob es so wichtig ist, aber es hilft dabei, die Stilkonsistenz Ihres Codes beizubehalten

NlightNFotis
quelle
-4

Nur eine kurze Ergänzung zu @ TimGilberts Antwort der alten Schule ... Sie können die init () -Methode Ihrer abstrakten Basisklasse dazu bringen , eine Ausnahme auszulösen, und das würde verhindern, dass sie instanziiert wird, nein?

>>> 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
quelle
6
Dadurch wird verhindert, dass Unterklassen von einem gemeinsamen Init- Code profitieren , der logischerweise in ihrer gemeinsamen Basisklasse vorhanden sein sollte.
Arpit Singh
Meinetwegen. Soviel zur alten Schule.
Dave Wade-Stein