Was ist die beste Vorgehensweise, um ein abstraktes Instanzattribut zu definieren, jedoch nicht als Eigenschaft?
Ich möchte etwas schreiben wie:
class AbstractFoo(metaclass=ABCMeta):
@property
@abstractmethod
def bar(self):
pass
class Foo(AbstractFoo):
def __init__(self):
self.bar = 3
Anstatt:
class Foo(AbstractFoo):
def __init__(self):
self._bar = 3
@property
def bar(self):
return self._bar
@bar.setter
def setbar(self, bar):
self._bar = bar
@bar.deleter
def delbar(self):
del self._bar
Eigenschaften sind praktisch, aber für einfache Attribute, die keine Berechnung erfordern, sind sie ein Overkill. Dies ist besonders wichtig für abstrakte Klassen , die vom Benutzer subclassed und umgesetzt werden (ich will nicht , dass jemand zu verwenden zwingen , @property
wenn er gerade geschrieben haben könnte self.foo = foo
in der __init__
).
Abstrakte Attribute in der Python- Frage werden als einzige zu verwendende Antwort vorgeschlagen @property
und @abstractmethod
: Meine Frage wird nicht beantwortet.
Das ActiveState-Rezept für ein abstraktes Klassenattribut über ist AbstractAttribute
möglicherweise der richtige Weg, aber ich bin mir nicht sicher. Es funktioniert auch nur mit Klassenattributen und nicht mit Instanzattributen.
Antworten:
Wenn Sie wirklich erzwingen möchten, dass eine Unterklasse ein bestimmtes Attribut definiert, können Sie eine Metaklasse verwenden. Persönlich denke ich, dass es übertrieben und nicht sehr pythonisch sein kann, aber Sie könnten so etwas tun:
class AbstractFooMeta(type): def __call__(cls, *args, **kwargs): """Called when you call Foo(*args, **kwargs) """ obj = type.__call__(cls, *args, **kwargs) obj.check_bar() return obj class AbstractFoo(object): __metaclass__ = AbstractFooMeta bar = None def check_bar(self): if self.bar is None: raise NotImplementedError('Subclasses must define bar') class GoodFoo(AbstractFoo): def __init__(self): self.bar = 3 class BadFoo(AbstractFoo): def __init__(self): pass
Grundsätzlich wird die Meta-Klasse neu definiert
__call__
, um sicherzustellen, dasscheck_bar
sie nach dem Init auf einer Instanz aufgerufen wird.GoodFoo() # ok BadFoo () # yield NotImplementedError
quelle
Es ist 2018, wir verdienen eine etwas bessere Lösung:
from better_abc import ABCMeta, abstract_attribute # see below class AbstractFoo(metaclass=ABCMeta): @abstract_attribute def bar(self): pass class Foo(AbstractFoo): def __init__(self): self.bar = 3 class BadFoo(AbstractFoo): def __init__(self): pass
Es wird sich so verhalten:
Foo() # ok BadFoo() # will raise: NotImplementedError: Can't instantiate abstract class BadFoo # with abstract attributes: bar
Diese Antwort verwendet den gleichen Ansatz wie die akzeptierte Antwort, lässt sich jedoch gut in das integrierte ABC integrieren und erfordert kein Hilfsprogramm
check_bar()
.Hier ist der
better_abc.py
Inhalt:from abc import ABCMeta as NativeABCMeta class DummyAttribute: pass def abstract_attribute(obj=None): if obj is None: obj = DummyAttribute() obj.__is_abstract_attribute__ = True return obj class ABCMeta(NativeABCMeta): def __call__(cls, *args, **kwargs): instance = NativeABCMeta.__call__(cls, *args, **kwargs) abstract_attributes = { name for name in dir(instance) if getattr(getattr(instance, name), '__is_abstract_attribute__', False) } if abstract_attributes: raise NotImplementedError( "Can't instantiate abstract class {} with" " abstract attributes: {}".format( cls.__name__, ', '.join(abstract_attributes) ) ) return instance
Das Schöne ist, dass Sie Folgendes tun können:
class AbstractFoo(metaclass=ABCMeta): bar = abstract_attribute()
und es wird genauso funktionieren wie oben.
Auch kann man verwenden:
class ABC(ABCMeta): pass
um einen benutzerdefinierten ABC-Helfer zu definieren. PS. Ich betrachte diesen Code als CC0.
Dies könnte verbessert werden, indem der AST-Parser verwendet wird, um früher (bei Klassendeklaration) durch Scannen des
__init__
Codes zu erhöhen , aber es scheint vorerst ein Overkill zu sein (es sei denn, jemand ist bereit zu implementieren).quelle
DummyAttribute
Einstellung erstellt haben, anstatt nur die Einstellungobj = object()
imabstract_attribute
Dekorator?obj = object()
in Ihrem Code ändern können, wenn Sie das bevorzugen.__setattr__
, um eine Validierung des angegebenen Werts durchzuführen, bevor das Attribut festgelegt wird. Es wird immer wieder ein Fehler ausgegeben, der besagt, dass das Attribut "schreibgeschützt" ist. Gibt es eine Möglichkeit, dies zu umgehen?Nur weil Sie es als
abstractproperty
abstrakte Basisklasse definieren , müssen Sie keine Eigenschaft für die Unterklasse erstellen.zB können Sie:
In [1]: from abc import ABCMeta, abstractproperty In [2]: class X(metaclass=ABCMeta): ...: @abstractproperty ...: def required(self): ...: raise NotImplementedError ...: In [3]: class Y(X): ...: required = True ...: In [4]: Y() Out[4]: <__main__.Y at 0x10ae0d390>
Wenn Sie den Wert in initialisieren möchten,
__init__
können Sie Folgendes tun:In [5]: class Z(X): ...: required = None ...: def __init__(self, value): ...: self.required = value ...: In [6]: Z(value=3) Out[6]: <__main__.Z at 0x10ae15a20>
Da Python 3.3
abstractproperty
ist veraltet . Daher sollten Python 3-Benutzer stattdessen Folgendes verwenden:from abc import ABCMeta, abstractmethod class X(metaclass=ABCMeta): @property @abstractmethod def required(self): raise NotImplementedError
quelle
required = None
Klassenkörper, wie oben gezeigt.@abstractproperty
obigen Definitionen erreicht, aber schlimmer ist, weil er keine Attribute behandelt, die im Klassenkörper definiert sind, anstatt__init__
Das Problem ist nicht was , sondern wann :
from abc import ABCMeta, abstractmethod class AbstractFoo(metaclass=ABCMeta): @abstractmethod def bar(): pass class Foo(AbstractFoo): bar = object() isinstance(Foo(), AbstractFoo) #>>> True
Es spielt keine Rolle, dass dies
bar
keine Methode ist! Das Problem ist, dass__subclasshook__
die Methode zur Durchführung der Prüfung a istclassmethod
und sich nur darum kümmert, ob die Klasse , nicht die Instanz , das Attribut hat.Ich schlage vor, Sie erzwingen dies einfach nicht, da es ein schwieriges Problem ist. Die Alternative besteht darin, sie zu zwingen, das Attribut vorab zu definieren, aber das lässt nur Dummy-Attribute übrig, die nur Fehler zum Schweigen bringen.
quelle
Wie Anentropic sagte, müssen Sie ein nicht
abstractproperty
als ein anderes implementierenproperty
.Eine Sache, die alle Antworten zu vernachlässigen scheinen, sind die Mitglieder-Slots von Python (das
__slots__
Klassenattribut). Benutzer Ihrer ABCs, die zur Implementierung abstrakter Eigenschaften erforderlich sind, können diese einfach innerhalb definieren,__slots__
wenn nur ein Datenattribut erforderlich ist.Also mit so etwas wie
class AbstractFoo(abc.ABC): __slots__ = () bar = abc.abstractproperty()
Benutzer können Unterklassen einfach wie folgt definieren:
class Foo(AbstractFoo): __slots__ = 'bar', # the only requirement # define Foo as desired def __init__(self): self.bar = ...
Hier
Foo.bar
verhält es sich wie ein reguläres Instanzattribut, das nur anders implementiert ist. Dies ist einfach, effizient und vermeidet die von@property
Ihnen beschriebene Boilerplate.Dies funktioniert unabhängig davon, ob ABCs
__slots__
an den Körpern ihrer Klasse definiert sind oder nicht . Wenn Sie den__slots__
gesamten Weg gehen, sparen Sie nicht nur Speicher und bieten schnellere Attributzugriffe, sondern auch einen aussagekräftigen Deskriptor, anstatt Zwischenprodukte (z. B.bar = None
oder ähnliches) in Unterklassen zu haben. 1Einige Antworten schlagen vor, die "abstrakte" Attributprüfung nach der Instanziierung durchzuführen (dh bei der Metaklassenmethode
__call__()
), aber ich finde, dass dies nicht nur verschwenderisch, sondern auch potenziell ineffizient ist, da der Initialisierungsschritt zeitaufwändig sein kann.Kurz gesagt, für Unterklassen von ABCs ist es erforderlich, den relevanten Deskriptor (sei es eine Eigenschaft oder eine Methode) zu überschreiben, egal wie, und Ihren Benutzern zu dokumentieren, dass es möglich ist, ihn
__slots__
als Implementierung für abstrakte Eigenschaften zu verwenden für mich als angemessener Ansatz.1 Zumindest sollten ABCs in jedem Fall immer ein leeres
__slots__
Klassenattribut definieren , da Unterklassen sonst gezwungen sind,__dict__
(dynamischen Attributzugriff) und__weakref__
(schwache Referenzunterstützung) zu haben, wenn sie instanziiert werden. Sieheabc
odercollections.abc
Module für Beispiele dies der Fall innerhalb der Standardbibliothek zu sein.quelle
Ich habe eine Weile danach gesucht, aber nichts gesehen, was mir gefällt. Wie Sie wahrscheinlich wissen, wenn Sie:
class AbstractFoo(object): @property def bar(self): raise NotImplementedError( "Subclasses of AbstractFoo must set an instance attribute " "self._bar in it's __init__ method") class Foo(AbstractFoo): def __init__(self): self.bar = "bar" f = Foo()
Sie bekommen eine,
AttributeError: can't set attribute
die nervt.Um dies zu umgehen, können Sie Folgendes tun:
class AbstractFoo(object): @property def bar(self): try: return self._bar except AttributeError: raise NotImplementedError( "Subclasses of AbstractFoo must set an instance attribute " "self._bar in it's __init__ method") class OkFoo(AbstractFoo): def __init__(self): self._bar = 3 class BadFoo(AbstractFoo): pass a = OkFoo() b = BadFoo() print a.bar print b.bar # raises a NotImplementedError
Dies vermeidet das,
AttributeError: can't set attribute
aber wenn Sie die abstrakte Eigenschaft einfach alle zusammen weglassen:class AbstractFoo(object): pass class Foo(AbstractFoo): pass f = Foo() f.bar
Sie erhalten eine,
AttributeError: 'Foo' object has no attribute 'bar'
die wohl fast so gut ist wie der NotImplementedError. Meine Lösung besteht also darin, nur eine Fehlermeldung von einer anderen zu tauschen. Und Sie müssen self._bar anstelle von self.bar in der Init verwenden .quelle
AbstractFoo
nicht abstrakt gemacht haben und diese Antwort völlig unabhängig von dem von OP festgelegten Kontext ist. Das heißt, Ihre Problemumgehung für das Problem "nicht einstellbares Attribut" der regulären Eigenschaften kann abhängig von der Häufigkeit des Zugriffs auf das Attribut etwas teuer sein. Ein billigerer (basierend auf Ihrem ersten Codeblock) ist dies in IhremFoo.__init__()
:self.__dict__['bar'] = "bar"
Wenn Sie https://docs.python.org/2/library/abc.html folgen, können Sie in Python 2.7 Folgendes tun:
from abc import ABCMeta, abstractproperty class Test(object): __metaclass__ = ABCMeta @abstractproperty def test(self): yield None def get_test(self): return self.test class TestChild(Test): test = None def __init__(self, var): self.test = var a = TestChild('test') print(a.get_test())
quelle