Was ist der kürzeste / eleganteste Weg, um den folgenden Scala-Code mit einem abstrakten Attribut in Python zu implementieren?
abstract class Controller {
val path: String
}
Eine Unterklasse von Controller
wird vom Scala-Compiler erzwungen, um "Pfad" zu definieren. Eine Unterklasse würde folgendermaßen aussehen:
class MyController extends Controller {
override val path = "/home"
}
python
oop
scala
abstract-class
Deamon
quelle
quelle
Antworten:
Python hat hierfür eine integrierte Ausnahme, die jedoch erst zur Laufzeit auftritt.
class Base(object): @property def path(self): raise NotImplementedError class SubClass(Base): path = 'blah'
quelle
NotImplementedError
expliziter und daher wahrscheinlich besser ist, als es einem zu überlassenAttributeError
.path
es direkt auf dem eingestellt istSubClass
. Wenn Sie in einer bestimmten Instanzsc = SubClass()
versuchen,sc.path = 'blah'
eine Methode festzulegen oder zu verwenden, die so etwas enthält,self.path = 'blah'
ohnepath
direkt zu definierenSubClass
, erhalten Sie eineAttributeError: can't set attribute
.Python 3.3+
from abc import ABCMeta, abstractmethod class A(metaclass=ABCMeta): def __init__(self): # ... pass @property @abstractmethod def a(self): pass @abstractmethod def b(self): pass class B(A): a = 1 def b(self): pass
Wenn Sie nicht deklarieren
a
oderb
in der abgeleiteten Klasse sind,B
wird FolgendesTypeError
ausgelöst:Python 2.7
Hierfür gibt es einen @ abstractproperty- Dekorateur:
from abc import ABCMeta, abstractmethod, abstractproperty class A: __metaclass__ = ABCMeta def __init__(self): # ... pass @abstractproperty def a(self): pass @abstractmethod def b(self): pass class B(A): a = 1 def b(self): pass
quelle
a
als Klasseneigenschaft vonB
anstelle einer Objekteigenschaft implementiert . Wenn Sie stattdessen `` `Klasse B (A): def __init __ (self): self.a = 1 ...` `` ausführen, wird beim Instanziieren ein Fehler angezeigtB
: "Die abstrakte Klasse B kann nicht mit abstrakten Methoden a instanziiert werden."class MyController(val path: String) extends Controller
ist eine wertvolle Option in der Scala-Version verfügbar, fehlt aber hierSeit diese Frage ursprünglich gestellt wurde, hat Python die Implementierung abstrakter Klassen geändert. Ich habe einen etwas anderen Ansatz verwendet, indem ich den abc.ABC-Formalismus in Python 3.6 verwendet habe. Hier definiere ich die Konstante als eine Eigenschaft, die in jeder Unterklasse definiert werden muss.
from abc import ABC, abstractmethod class Base(ABC): @property @classmethod @abstractmethod def CONSTANT(cls): return NotImplementedError def print_constant(self): print(type(self).CONSTANT) class Derived(Base): CONSTANT = 42
Dadurch wird die abgeleitete Klasse gezwungen, die Konstante zu definieren. Andernfalls wird eine
TypeError
Ausnahme ausgelöst, wenn Sie versuchen, die Unterklasse zu instanziieren. Wenn Sie die Konstante für eine in der abstrakten Klasse implementierte Funktionalität verwenden möchten, müssen Sie über auf die Unterklassenkonstante zugreifentype(self).CONSTANT
statt nurCONSTANT
, da der Wert in der Basisklasse undefiniert ist.Es gibt andere Möglichkeiten, dies zu implementieren, aber ich mag diese Syntax, da sie mir für den Leser am einfachsten und offensichtlichsten erscheint.
Die vorherigen Antworten haben alle nützliche Punkte berührt, aber ich bin der Meinung, dass die akzeptierte Antwort die Frage nicht direkt beantwortet, weil
CONSTANT
nicht definiert ist. Die akzeptierte Antwort ermöglicht die Instanziierung des Objekts und gibt nur beimCONSTANT
Zugriff einen Fehler aus , wodurch die Durchsetzung weniger streng wird.Dies ist kein Fehler bei den ursprünglichen Antworten. Seit der Veröffentlichung wurden wesentliche Änderungen an der Syntax der abstrakten Klasse vorgenommen, die in diesem Fall eine übersichtlichere und funktionalere Implementierung ermöglichen.
quelle
mypy
print_constant
Methode ist? Soweit ich das beurteilen kann, macht es nichts?print_constant
Methode soll veranschaulichen, wie auf die Konstante in der übergeordneten Klasse zugegriffen wird, da sie nur in der untergeordneten Klasse definiert istBase.CONSTANT
überprüft mypy auch, ob die Unterklasse den richtigen Typ für die Definition von verwendetCONSTANT
.In Python 3.6+ können Sie ein Attribut einer abstrakten Klasse (oder einer beliebigen Variablen) mit Anmerkungen versehen, ohne einen Wert für dieses Attribut anzugeben .
class Controller: path: str class MyController(Controller): path = "/home"
Dies führt zu sehr sauberem Code, bei dem es offensichtlich ist, dass das Attribut abstrakt ist. Code, der versucht, auf das Attribut zuzugreifen, wenn er nicht überschrieben wurde, löst einen aus
AttributeError
.quelle
Sie können ein Attribut in der abstrakten Basisklasse abc.ABC mit einem Wert erstellen, z. B.
NotImplemented
wenn das Attribut nicht überschrieben und dann verwendet wird, wird zur Laufzeit ein Fehler angezeigt.Der folgende Code verwendet einen PEP 484- Typhinweis, um PyCharm dabei zu helfen, den Typ des
path
Attributs auch statisch korrekt zu analysieren .import abc class Controller(abc.ABC): path: str = NotImplemented class MyController(Controller): path = "/home"
quelle
Für Python 3.3 + gibt es eine elegante Lösung
from abc import ABC, abstractmethod class BaseController(ABC): @property @abstractmethod def path(self) -> str: ... class Controller(BaseController): path = "/home" # Instead of an elipsis, you can add a docstring, # This approach provides more clarity class AnotherBaseController(ABC): @property @abstractmethod def path(self) -> str: """ :return: the url path of this controller """
Wie unterscheidet es sich von anderen Antworten:
...
in einer abstrakten Methode ist der Körper vorzuziehen alspass
. Im Gegensatz zupass
,...
impliziert keine Operationen , wopass
nur das Fehlen einer tatsächlichen Implementierung bedeutet ,...
ist mehr zu empfehlen als zu werfenNotImplementedError(...)
. Dies führt automatisch zu einem äußerst ausführlichen Fehler, wenn die Implementierung eines abstrakten Felds in einer Unterklasse fehlt. Im Gegensatz dazuNotImplementedError
sagt selbst nicht, warum die Implementierung fehlt. Darüber hinaus erfordert es Handarbeit, um es tatsächlich anzuheben.quelle
...
oder erhöhenNotImplementedError
oder einfachpass
keinen Einfluss auf den Fehler haben, der beim Instanziieren einer abstrakten Klasse (oder Unterklasse) mit nicht implementierten Methoden auftritt.pass
oder einer elipsis (...
) ist es besser, mindestens eine Dokumentzeichenfolge für die abstrakte Methode zu deklarieren. Pylint hat mir das gesagt.Ab Python 3.6 können Sie
__init_subclass__
bei der Initialisierung nach den Klassenvariablen der untergeordneten Klasse suchen:from abc import ABC class A(ABC): @classmethod def __init_subclass__(cls): required_class_variables = [ "foo", "bar" ] for var in required_class_variables: if not hasattr(cls, var): raise NotImplementedError( f'Class {cls} lacks required `{var}` class attribute' )
Dies führt zu einem Fehler bei der Initialisierung der untergeordneten Klasse, wenn die fehlende Klassenvariable nicht definiert ist, sodass Sie nicht warten müssen, bis auf die fehlende Klassenvariable zugegriffen wird.
quelle
__init_subclass__
der übergeordneten Klasse wird aufgerufen, bevor die Unterklasse initialisiert wird. Wenn Sie also nach Eigenschaften suchen möchten, die in den Unterklassen festgelegt sind__init__
, funktioniert dies nicht.__init__
Methode festgelegt werden.Ihre Basisklasse könnte eine
__new__
Methode implementieren , die nach Klassenattributen sucht:class Controller(object): def __new__(cls, *args, **kargs): if not hasattr(cls,'path'): raise NotImplementedError("'Controller' subclasses should have a 'path' attribute") return object.__new__(cls) class C1(Controller): path = 42 class C2(Controller): pass c1 = C1() # ok c2 = C2() # NotImplementedError: 'Controller' subclasses should have a 'path' attribute
Auf diese Weise wird der Fehler bei der Instanziierung ausgelöst
quelle
Die Implementierung von Python3.6 könnte folgendermaßen aussehen:
In [20]: class X: ...: def __init_subclass__(cls): ...: if not hasattr(cls, 'required'): ...: raise NotImplementedError In [21]: class Y(X): ...: required = 5 ...: In [22]: Y() Out[22]: <__main__.Y at 0x7f08408c9a20>
quelle
Ich habe nur ein bisschen @ James Antwort geändert , damit all diese Dekorateure nicht so viel Platz einnehmen. Wenn Sie mehrere solche abstrakten Eigenschaften definieren mussten, ist dies praktisch:
from abc import ABC, abstractmethod def abstractproperty(func): return property(classmethod(abstractmethod(func))) class Base(ABC): @abstractproperty def CONSTANT(cls): ... def print_constant(self): print(type(self).CONSTANT) class Derived(Base): CONSTANT = 42 class BadDerived(Base): BAD_CONSTANT = 42 Derived() # -> Fine BadDerived() # -> Error
quelle
Schauen Sie sich das Modul abc (Abtract Base Class) an: http://docs.python.org/library/abc.html
Meiner Meinung nach besteht die einfachste und häufigste Lösung darin, eine Ausnahme auszulösen, wenn eine Instanz der Basisklasse erstellt wird oder wenn auf ihre Eigenschaft zugegriffen wird.
quelle
Bastien Léonards Antwort erwähnt das abstrakte Basisklassenmodul und Brendan Abels Antwort befasst sich mit nicht implementierten Attributen, die Fehler verursachen. Um sicherzustellen, dass die Klasse nicht außerhalb des Moduls implementiert ist, können Sie dem Basisnamen einen Unterstrich voranstellen, der ihn als privat für das Modul kennzeichnet (dh nicht importiert).
dh
class _Controller(object): path = '' # There are better ways to declare attributes - see other answers class MyController(_Controller): path = '/Home'
quelle
_Controller
Klasse wegzulassen? Duck Typing wird nicht wirksam, wenn bereits ein (ungültiger) Wert vorhanden ist. Andernfalls würde irgendwann, wenn daspath
Feld definiert werden muss, kein Fehler auftreten, da bereits ein Wert vorhanden ist.class AbstractStuff: @property @abc.abstractmethod def some_property(self): pass
Ab 3.3
abc.abstractproperty
ist ich veraltet.quelle