457 Stimmen

Ist es möglich, abstrakte Klassen zu erstellen?

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

Ich habe versucht, eine neue Definition __new__() etwa so:

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

Aber wenn ich jetzt eine Klasse erstelle G die erbt von F etwa so:

class G(F):
    pass

Dann kann ich nicht instanziieren G auch nicht, denn sie ruft die Super-Klasse __new__ Methode.

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

756voto

alexvassel Punkte 10250

Verwenden Sie die abc Modul, um abstrakte Klassen zu erstellen. Verwenden Sie das abstractmethod Dekorator, um eine Methode als abstrakt zu deklarieren, und deklarieren Sie eine Klasse auf eine der drei Arten abstrakt, abhängig von Ihrer Python-Version.

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

# 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, welchen Weg Sie wählen, können Sie keine abstrakte Klasse instanziieren, die über abstrakte Methoden verfügt, aber Sie können eine Unterklasse instanziieren, die konkrete Definitionen für diese Methoden bereitstellt:

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

152voto

Tim Gilbert Punkte 5431

Die alte Schule (vor PEP 3119 ) können Sie dies tun, indem Sie einfach raise NotImplementedError in der abstrakten Klasse, 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 dieselben angenehmen Eigenschaften wie die Verwendung der abc Modul tut. Sie können die abstrakte Basisklasse immer noch selbst instanziieren, und Sie werden Ihren Fehler erst feststellen, wenn Sie die abstrakte Methode zur Laufzeit aufrufen.

Aber wenn Sie es mit einer kleinen Anzahl einfacher Klassen zu tun haben, vielleicht mit nur ein paar abstrakten Methoden, ist dieser Ansatz etwas einfacher als der Versuch, sich durch die abc Dokumentation.

29voto

Irv Kalb Punkte 319

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

In der __init__ Methode der Klasse, die eine abstrakte Klasse sein soll, können Sie den "Typ" von self überprüfen. Wenn der Typ von self die Basisklasse ist, dann versucht der Aufrufer, die Basisklasse zu instanziieren, und löst eine Ausnahme aus. 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()

Wenn es ausgeführt wird, erzeugt es:

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, aber Sie können die Basisklasse nicht direkt instanziieren.

24voto

grepit Punkte 18534

Die meisten früheren Antworten waren richtig, 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 logischerweise zu einer Klasse gehört, aber diese Klasse kann nicht angeben, wie die Methode implementiert werden soll. In den folgenden Klassen Parents und Babies zum Beispiel essen beide, aber die Implementierung wird für jede Klasse anders sein, weil Babys und Eltern eine andere Art von Nahrung essen und die Anzahl der Mahlzeiten unterschiedlich ist. Daher überschreibt die Methode eat in den Unterklassen 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())

OUTPUT:

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

17voto

Cleb Punkte 22735

Wie in den anderen Antworten erläutert, können Sie abstrakte Klassen in Python mit der Option abc Modul . Im Folgenden gebe ich ein konkretes Beispiel mit abstrakten @classmethod , @property y @abstractmethod (mit Python 3.6+). Für mich ist es in der Regel 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 namens 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

Unser Base Klasse hat immer eine from_dict classmethod , a property prop1 (die schreibgeschützt ist) und eine property prop2 (die auch gesetzt werden kann) sowie eine Funktion namens do_stuff . Welche Klasse auch immer nun auf der Grundlage von Base müssen alle diese vier Methoden/Eigenschaften implementieren. Bitte beachten Sie, dass für eine abstrakte Methode zwei Dekoratoren erforderlich sind - classmethod und abstrakt property .

Jetzt könnten wir eine Klasse erstellen A wie diese:

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: i_am_not_abstract ).

Jetzt können wir es 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 die prop1 :

a.prop1 = 100

wird zurückgegeben

AttributeError: Attribut kann nicht gesetzt werden

Auch unser from_dict Methode funktioniert gut:

a2.prop1
# prints 20

Wenn wir nun eine zweite Klasse definieren B wie diese:

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

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

und versucht, ein Objekt wie dieses zu instanziieren:

b = B('iwillfail')

erhalten wir eine Fehlermeldung

TypFehler: Kann die abstrakte Klasse B mit abstrakten Methoden nicht instanziieren do_stuff, from_dict, prop2

Auflistung aller Dinge, die in Base die wir in den folgenden Bereichen nicht umgesetzt haben B .

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X