Nehmen wir an, wir möchten eine Klassenfamilie erstellen, bei der es sich um unterschiedliche Implementierungen oder Spezialisierungen eines übergreifenden Konzepts handelt. Nehmen wir an, dass es für einige abgeleitete Eigenschaften eine plausible Standardimplementierung gibt. Wir möchten dies in eine Basisklasse einordnen
class Math_Set_Base:
@property
def size(self):
return len(self.elements)
Eine Unterklasse kann also automatisch ihre Elemente in diesem ziemlich dummen Beispiel zählen
class Concrete_Math_Set(Math_Set_Base):
def __init__(self,*elements):
self.elements = elements
Concrete_Math_Set(1,2,3).size
# 3
Was aber, wenn eine Unterklasse diese Standardeinstellung nicht verwenden möchte? Das funktioniert nicht:
import math
class Square_Integers_Below(Math_Set_Base):
def __init__(self,cap):
self.size = int(math.sqrt(cap))
Square_Integers_Below(7)
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# File "<stdin>", line 3, in __init__
# AttributeError: can't set attribute
Mir ist klar, dass es Möglichkeiten gibt, eine Eigenschaft mit einer Eigenschaft zu überschreiben, aber ich möchte das vermeiden. Weil der Zweck der Basisklasse darin besteht, dem Benutzer das Leben so einfach wie möglich zu machen und kein Aufblähen hinzuzufügen, indem eine (aus enger Sicht der Unterklasse) verschlungene und überflüssige Zugriffsmethode auferlegt wird.
Kann es gemacht werden? Wenn nicht, was ist die nächstbeste Lösung?
quelle
__get__()
,__set__()
und__delete__()
) , und sie heben ein ,AttributeError
wenn Sie für sie jede Funktion nicht zur Verfügung stellen. Siehe Python-Äquivalent zur Eigenschaftsimplementierung .setter
oder__set__
das Eigentum kümmern , wird dies funktionieren. Ich möchte Sie jedoch darauf hinweisen, dass dies auch bedeutet, dass Sie die Eigenschaft nicht nur auf Klassenebene (self.size = ...
), sondern auch auf Instanzebene (z. B.Concrete_Math_Set(1, 2, 3).size = 10
gleichermaßen gültig) leicht überschreiben können . Denkanstoß :)Square_Integers_Below(9).size = 1
ist auch gültig, da es sich um ein einfaches Attribut handelt. In diesem speziellen Anwendungsfall "Größe" mag dies akward erscheinen (verwenden Sie stattdessen eine Eigenschaft), aber im allgemeinen Fall "eine berechnete Requisite in der Unterklasse leicht überschreiben" kann dies in einigen Fällen gut sein. Sie können den Attributzugriff auch mit steuern__setattr__()
, dies kann jedoch überwältigend sein.Dies wird eine langwierige Antwort sein, die möglicherweise nur als Komplementär dient ... aber Ihre Frage hat mich zu einer Fahrt durch das Kaninchenloch geführt, sodass ich auch meine Erkenntnisse (und Schmerzen) mitteilen möchte.
Möglicherweise ist diese Antwort für Ihr eigentliches Problem nicht hilfreich. In der Tat ist meine Schlussfolgerung, dass - ich würde das überhaupt nicht tun. Der Hintergrund dieser Schlussfolgerung könnte Sie jedoch ein wenig unterhalten, da Sie nach weiteren Details suchen.
Ein Missverständnis ansprechen
Die erste Antwort ist zwar in den meisten Fällen richtig, aber nicht immer der Fall. Betrachten Sie zum Beispiel diese Klasse:
inst_prop
ist einproperty
unwiderrufliches Instanzattribut:Es hängt alles davon , wo Ihr
property
in der ersten Stelle definiert. Wenn Ihr@property
innerhalb der Klasse "scope" (oder wirklich dernamespace
) definiert ist, wird es zu einem Klassenattribut. In meinem Beispiel sind der Klasse selbst keine bekannt,inst_prop
bis sie instanziiert wird. Natürlich ist es als Immobilie hier überhaupt nicht sehr nützlich.Aber lassen Sie uns zuerst Ihren Kommentar zur Auflösung der Vererbung ansprechen ...
Wie genau spielt die Vererbung in diesem Problem eine Rolle? Dieser folgende Artikel befasst sich ein wenig mit dem Thema, und die Reihenfolge der Methodenauflösung ist etwas verwandt, obwohl hauptsächlich die Breite der Vererbung anstelle der Tiefe behandelt wird.
Kombiniert mit unserem Ergebnis, angesichts der folgenden Einstellungen:
Stellen Sie sich vor, was passiert, wenn diese Zeilen ausgeführt werden:
Das Ergebnis ist also:
Beachte wie:
self.world_view
konnte angewendet werden, währendself.culture
fehlgeschlagenculture
existiert nicht inChild.__dict__
(dermappingproxy
Klasse, nicht zu verwechseln mit der Instanz__dict__
)culture
existiertc.__dict__
, wird nicht darauf verwiesen.Möglicherweise können Sie erraten, warum -
world_view
wurde von derParent
Klasse als Nicht-EigenschaftChild
überschrieben , sodass Sie es auch überschreiben konnten. In der Zwischenzeit existiert es, daculture
es vererbt wird, nur innerhalbmappingproxy
vonGrandparent
:In der Tat, wenn Sie versuchen zu entfernen
Parent.culture
:Sie werden feststellen, dass es nicht einmal existiert
Parent
. Weil sich das Objekt direkt auf verweistGrandparent.culture
.Was ist also mit der Resolution Order?
Wir sind also daran interessiert, die tatsächliche Auflösungsreihenfolge zu beachten. Versuchen wir
Parent.world_view
stattdessen , Folgendes zu entfernen :Frage mich, was das Ergebnis ist?
Es kehrte zu Großeltern zurück
world_view
property
, obwohl wir es erfolgreich geschafft hatten, dasself.world_view
vorher zuzuweisen ! Aber was ist, wenn wir unsworld_view
auf Klassenebene gewaltsam ändern , wie die andere Antwort? Was ist, wenn wir es löschen? Was ist, wenn wir das aktuelle Klassenattribut einer Eigenschaft zuweisen?Das Ergebnis ist:
Dies ist interessant, da das
c.world_view
Instanzattribut wiederhergestellt wird, währendChild.world_view
es das von uns zugewiesene ist. Nach dem Entfernen des Instanzattributs wird auf das Klassenattribut zurückgegriffen. Und nachdemChild.world_view
wir das der Eigenschaft neu zugewiesen haben , verlieren wir sofort den Zugriff auf das Instanzattribut.Daher können wir die folgende Auflösungsreihenfolge vermuten :
property
, rufen Sie seinen Wert übergetter
oder abfget
(dazu später mehr). Aktuelle Klasse zuerst bis Basisklasse zuletzt.property
. Aktuelle Klasse zuerst bis Basisklasse zuletzt.In diesem Fall entfernen wir die Wurzel
property
:Welches gibt:
TA Dah!
Child
hat jetzt ihre eigeneculture
basierend auf dem kraftvollen Einfügen inc.__dict__
.Child.culture
existiert natürlich nicht, da es nie inParent
oderChild
Klassenattribut definiert wurde undGrandparent
's entfernt wurde.Ist das die Hauptursache für mein Problem?
Eigentlich nein . Der Fehler, den Sie beim Zuweisen immer noch beobachten
self.culture
, ist völlig anders . Aber die Vererbungsreihenfolge bildet den Hintergrund für die Antwort - die dasproperty
Selbst ist.Haben Sie neben der zuvor erwähnten
getter
Methodeproperty
auch ein paar nette Tricks im Ärmel. Am relevantesten ist in diesem Fall die Methodesetter
oderfset
, die durch dieself.culture = ...
Leitung ausgelöst wird . Da Sieproperty
keinesetter
oder keinefget
Funktion implementiert haben , weiß Python nicht, was zu tun ist, und wirftAttributeError
stattdessen eine (dhcan't set attribute
).Wenn Sie jedoch eine
setter
Methode implementiert haben :Wenn Sie die
Child
Klasse instanziieren , erhalten Sie:Anstatt eine zu erhalten
AttributeError
, rufen Sie jetzt tatsächlich diesome_prop.setter
Methode auf. Dadurch haben Sie mehr Kontrolle über Ihr Objekt. Aufgrund unserer früheren Erkenntnisse wissen wir, dass ein Klassenattribut überschrieben werden muss, bevor es die Eigenschaft erreicht. Dies könnte innerhalb der Basisklasse als Trigger implementiert werden. Hier ist ein neues Beispiel:Was in ... resultiert:
TA DAH! Sie können jetzt Ihr eigenes Instanzattribut über eine geerbte Eigenschaft überschreiben!
Also, Problem gelöst?
... Nicht wirklich. Das Problem bei diesem Ansatz ist, dass Sie jetzt keine richtige
setter
Methode haben können. Es gibt Fälle, in denen Sie Werte für Ihre festlegen möchtenproperty
. Aber jetzt , wenn stellen Sieself.culture = ...
sie immer überschreiben , was Funktion , die Sie in der definiertgetter
(was in diesem Fall wirklich nur der ist@property
umschlungenen Bereich. Sie können in differenzierteren Maßnahmen hinzufügen, aber eine oder andere Weise , es wird immer mehr als nur beinhaltenself.culture = ...
. z.B:Es ist auf Klassenebene komplizierter als die andere Antwort
size = None
.Sie können auch einen eigenen Deskriptor schreiben , um die Methoden
__get__
und__set__
oder zusätzliche Methoden zu verarbeiten. Aber am Ende des Tages, wennself.culture
verwiesen wird,__get__
wird immer zuerst ausgelöst, und wennself.culture = ...
verwiesen wird,__set__
wird immer zuerst ausgelöst. Soweit ich es versucht habe, führt kein Weg daran vorbei.Der Kern des Problems, IMO
Das Problem, das ich hier sehe, ist - Sie können Ihren Kuchen nicht haben und ihn auch essen.
property
ist wie ein Deskriptor mit bequemem Zugriff von Methoden wiegetattr
oder gemeintsetattr
. Wenn Sie auch möchten, dass diese Methoden einen anderen Zweck erfüllen, fragen Sie nur nach Problemen. Ich würde vielleicht den Ansatz überdenken:property
dafür?property
, gibt es einen Grund, warum ich sie überschreiben müsste?property
nicht zutreffen?property
s überschreiben muss , würde mir eine separate Methode besser dienen als nur eine Neuzuweisung, da eine Neuzuweisung dieproperty
s versehentlich ungültig machen kann ?Für Punkt 5 wäre mein Ansatz eine
overwrite_prop()
Methode in der Basisklasse, die das aktuelle Klassenattribut überschreibt, so dass dasproperty
nicht mehr ausgelöst wird:Wie Sie sehen können, ist es, obwohl es noch ein bisschen erfunden ist, zumindest expliziter als ein kryptisches
size = None
. Letztendlich würde ich die Eigenschaft jedoch überhaupt nicht überschreiben und mein Design von Grund auf überdenken.Wenn Sie es bis hierher geschafft haben - danke, dass Sie diese Reise mit mir gegangen sind. Es war eine lustige kleine Übung.
quelle
A
@property
wird auf Klassenebene definiert. In der Dokumentation wird ausführlich beschrieben, wie es funktioniert. Es genügt jedoch zu sagen, dass das Festlegen oder Abrufen der Eigenschaft zum Aufrufen einer bestimmten Methode erfolgt. Dasproperty
Objekt, das diesen Prozess verwaltet, wird jedoch mit der eigenen Definition der Klasse definiert. Das heißt, es ist als Klassenvariable definiert, verhält sich jedoch wie eine Instanzvariable.Eine Folge davon ist, dass Sie es auf Klassenebene frei neu zuweisen können :
Und genau wie bei jedem anderen Namen auf Klassenebene (z. B. Methoden) können Sie ihn in einer Unterklasse überschreiben, indem Sie ihn einfach explizit anders definieren:
Wenn wir eine tatsächliche Instanz erstellen, schattiert die Instanzvariable einfach die gleichnamige Klassenvariable. Das
property
Objekt verwendet normalerweise einige Spielereien, um diesen Prozess zu manipulieren (dh Getter und Setter anwenden). Wenn der Name auf Klassenebene jedoch nicht als Eigenschaft definiert ist, geschieht nichts Besonderes und verhält sich so, wie Sie es von jeder anderen Variablen erwarten würden.quelle
__dict__
? Gibt es auch Möglichkeiten, dies zu automatisieren? Ja, es ist nur eine Zeile, aber es ist sehr kryptisch zu lesen, wenn Sie nicht mit den blutigen Details von Eigenschaften usw. vertraut sind# declare size to not be a @property
Sie brauchen überhaupt keine Zuordnung
size
.size
ist eine Eigenschaft in der Basisklasse, sodass Sie diese Eigenschaft in der untergeordneten Klasse überschreiben können:Sie können dies (mikro) optimieren, indem Sie die Quadratwurzel vorberechnen:
quelle
size
eine Eigenschaft und kein Instanzattribut ist, müssen Sie nichts Besonderes tun .Es sieht so aus, als ob Sie definieren möchten
size
in Ihrer Klasse :Eine andere Möglichkeit besteht darin,
cap
in Ihrer Klasse zu speichern und diese mit einersize
als Eigenschaft definierten Eigenschaft zu berechnen (die die Eigenschaft der Basisklasse überschreibtsize
).quelle
Ich schlage vor, einen Setter wie folgt hinzuzufügen:
Auf diese Weise können Sie die Standardeigenschaft
.size
wie folgt überschreiben :quelle
Auch können Sie als nächstes tun
quelle
_size
oder_size_call
da. Sie könnten den Funktionsaufruf innerhalbsize
als Bedingung eingebrannt undtry... except...
zum Testen verwendet haben,_size
anstatt eine zusätzliche Klassenreferenz zu verwenden, die nicht verwendet wird. Ungeachtet dessen halte ich dies für noch kryptischer als das einfachesize = None
Überschreiben.