Lieber Eigenschaften . Dafür sind sie da.
Der Grund ist, dass alle Attribute in Python öffentlich sind. Das Starten von Namen mit einem oder zwei Unterstrichen ist nur eine Warnung, dass das angegebene Attribut ein Implementierungsdetail ist, das in zukünftigen Versionen des Codes möglicherweise nicht gleich bleibt. Es hindert Sie nicht daran, dieses Attribut tatsächlich zu erhalten oder festzulegen. Daher ist der Standardzugriff auf Attribute die normale pythonische Methode, um auf Attribute zuzugreifen.
Der Vorteil von Eigenschaften besteht darin, dass sie syntaktisch mit dem Attributzugriff identisch sind, sodass Sie von einem zum anderen wechseln können, ohne den Clientcode zu ändern. Sie könnten sogar eine Version einer Klasse haben, die Eigenschaften verwendet (z. B. für Code-by-Contract oder Debugging), und eine, die nicht für die Produktion verwendet wird, ohne den Code zu ändern, der sie verwendet. Gleichzeitig müssen Sie nicht für alles Getter und Setter schreiben, nur für den Fall, dass Sie den Zugriff später besser steuern müssen.
In Python verwenden Sie Getter oder Setter oder Eigenschaften nicht nur zum Spaß. Sie verwenden zuerst nur Attribute und migrieren später, nur bei Bedarf, zu einer Eigenschaft, ohne den Code mithilfe Ihrer Klassen ändern zu müssen.
Es gibt in der Tat viel Code mit der Erweiterung .py, der Getter und Setter sowie Vererbung und sinnlose Klassen überall dort verwendet, wo beispielsweise ein einfaches Tupel ausreichen würde, aber es ist Code von Leuten, die mit Python in C ++ oder Java schreiben.
Das ist kein Python-Code.
quelle
Wenn Sie Eigenschaften verwenden, können Sie mit normalen Attributzugriffen beginnen und diese anschließend bei Bedarf mit Getter und Setter sichern .
quelle
Die kurze Antwort lautet: Eigenschaften gewinnen zweifellos . Immer.
Es besteht manchmal ein Bedarf an Gettern und Setzern, aber selbst dann würde ich sie nach außen "verstecken". Es gibt viele Möglichkeiten , dies in Python zu tun (
getattr
,setattr
,__getattribute__
, etc ..., aber ein sehr präzise und sauber ist:Hier ist ein kurzer Artikel , der das Thema Getter und Setter in Python einführt.
quelle
property
. (Es sieht aus wie eine einfache Aufgabe, nennt aber eine Funktion.) Daher ist "Pythonic" im Wesentlichen ein bedeutungsloser Begriff, außer durch die tautologische Definition: "Pythonic-Konventionen sind Dinge, die wir als Pythonic definiert haben."property
Feature die Idee der pythonischen Axiome nahezu wertlos zu machen droht. Wir haben also nur noch eine Checkliste.self
Objekts hat , können explizite Setter hilfreich sein. Esuser.email = "..."
sieht beispielsweise nicht so aus, als könnte es eine Ausnahme auslösen, da es so aussieht, als würde nur ein Attribut festgelegt, währenduser.set_email("...")
klar ist, dass es Nebenwirkungen wie Ausnahmen geben kann.[ TL; DR? Sie können bis zum Ende springen, um ein Codebeispiel zu erhalten .]
Eigentlich bevorzuge ich die Verwendung einer anderen Redewendung, die für die einmalige Verwendung etwas aufwendig ist, aber schön ist, wenn Sie einen komplexeren Anwendungsfall haben.
Zuerst ein bisschen Hintergrund.
Eigenschaften sind insofern nützlich, als sie es uns ermöglichen, sowohl das Einstellen als auch das Abrufen von Werten programmgesteuert zu behandeln, aber dennoch den Zugriff auf Attribute als Attribute zu ermöglichen. Wir können "Gets" (im Wesentlichen) in "Berechnungen" und "Sets" in "Ereignisse" verwandeln. Nehmen wir also an, wir haben die folgende Klasse, die ich mit Java-ähnlichen Gettern und Setzern codiert habe.
Sie fragen sich vielleicht, warum ich nicht aufgerufen habe
defaultX
unddefaultY
in der__init__
Methode des Objekts . Der Grund ist, dass ich für unseren Fall annehmen möchte, dass diesomeDefaultComputation
Methoden Werte zurückgeben, die sich über die Zeit ändern, beispielsweise einen Zeitstempel, und wann immerx
(odery
) nicht gesetzt ist (wobei für den Zweck dieses Beispiels "nicht gesetzt" "gesetzt" bedeutet to None ") Ich möchte den Wert der Standardberechnung vonx
's (odery
' s).Dies ist aus einer Reihe von Gründen, die oben beschrieben wurden, lahm. Ich werde es mit Eigenschaften umschreiben:
Was haben wir gewonnen? Wir haben die Möglichkeit erhalten, diese Attribute als Attribute zu bezeichnen, obwohl wir hinter den Kulissen Methoden ausführen.
Die wahre Stärke von Eigenschaften besteht natürlich darin, dass diese Methoden im Allgemeinen nicht nur Werte abrufen und festlegen sollen (andernfalls macht es keinen Sinn, Eigenschaften zu verwenden). Ich habe das in meinem Getter-Beispiel gemacht. Grundsätzlich führen wir einen Funktionskörper aus, um einen Standardwert zu ermitteln, wenn der Wert nicht festgelegt ist. Dies ist ein sehr verbreitetes Muster.
Aber was verlieren wir und was können wir nicht tun?
Meiner Ansicht nach ist der größte Ärger, dass Sie, wenn Sie einen Getter definieren (wie wir es hier tun), auch einen Setter definieren müssen. [1] Das ist zusätzliches Rauschen, das den Code überfüllt.
Ein weiterer Ärger ist, dass wir die
x
undy
-Werte noch initialisieren müssen__init__
. (Nun, natürlich könnten wir sie mit hinzufügen,setattr()
aber das ist mehr zusätzlicher Code.)Drittens können Getter im Gegensatz zum Java-ähnlichen Beispiel keine anderen Parameter akzeptieren. Jetzt kann ich Sie schon sagen hören, na ja, wenn es Parameter nimmt, ist es kein Getter! Im offiziellen Sinne ist das wahr. In praktischer Hinsicht gibt es jedoch keinen Grund, warum wir nicht in der Lage sein sollten, ein benanntes Attribut wie zu parametrisieren
x
und seinen Wert für bestimmte Parameter festzulegen.Es wäre schön, wenn wir so etwas machen könnten:
zum Beispiel. Das Beste, was wir bekommen können, ist, die Zuweisung zu überschreiben, um eine spezielle Semantik zu implizieren:
und natürlich sicherstellen, dass unser Setter weiß, wie man die ersten drei Werte als Schlüssel für ein Wörterbuch extrahiert und seinen Wert auf eine Zahl oder etwas setzt.
Aber selbst wenn wir das tun würden, könnten wir es nicht mit Eigenschaften unterstützen, da es keine Möglichkeit gibt, den Wert abzurufen, da wir überhaupt keine Parameter an den Getter übergeben können. Wir mussten also alles zurückgeben und eine Asymmetrie einführen.
Der Java-artige Getter / Setter lässt uns damit umgehen, aber wir brauchen wieder Getter / Setter.
In meinen Augen wollen wir wirklich etwas, das die folgenden Anforderungen erfüllt:
Benutzer definieren nur eine Methode für ein bestimmtes Attribut und können dort angeben, ob das Attribut schreibgeschützt oder schreibgeschützt ist. Eigenschaften schlagen diesen Test fehl, wenn das Attribut beschreibbar ist.
Der Benutzer muss keine zusätzliche Variable definieren, die der Funktion zugrunde liegt, daher benötigen wir das
__init__
odersetattr
im Code nicht. Die Variable existiert nur dadurch, dass wir dieses Attribut im neuen Stil erstellt haben.Jeder Standardcode für das Attribut wird im Methodenkörper selbst ausgeführt.
Wir können das Attribut als Attribut festlegen und als Attribut referenzieren.
Wir können das Attribut parametrisieren.
In Bezug auf Code wollen wir eine Möglichkeit zu schreiben:
und dann in der Lage sein:
und so weiter.
Wir möchten dies auch für den Sonderfall eines parametrierbaren Attributs tun, aber dennoch zulassen, dass der Standard-Zuweisungsfall funktioniert. Sie werden unten sehen, wie ich das angegangen bin.
Nun zum Punkt (yay! Der Punkt!). Die Lösung, die ich dafür gefunden habe, ist wie folgt.
Wir erstellen ein neues Objekt, um den Begriff einer Eigenschaft zu ersetzen. Das Objekt soll den Wert einer darauf gesetzten Variablen speichern, verwaltet jedoch auch ein Handle für Code, der weiß, wie ein Standard berechnet wird. Seine Aufgabe ist es, die Menge zu speichern
value
oder die auszuführen,method
wenn dieser Wert nicht gesetzt ist.Nennen wir es ein
UberProperty
.Ich nehme an,
method
hier ist eine Klassenmethode,value
ist der Wert vonUberProperty
, und ich habe hinzugefügt,isSet
weil es sichNone
möglicherweise um einen echten Wert handelt, und dies ermöglicht uns eine saubere Möglichkeit, zu erklären, dass es wirklich "keinen Wert" gibt. Ein anderer Weg ist eine Art Wachposten.Dies gibt uns im Grunde ein Objekt, das tun kann, was wir wollen, aber wie setzen wir es tatsächlich in unsere Klasse ein? Nun, Eigenschaften verwenden Dekorateure; warum können wir nicht Mal sehen, wie es aussehen könnte (von jetzt an werde ich nur noch ein einziges 'Attribut' verwenden
x
).Das funktioniert natürlich noch nicht. Wir müssen implementieren
uberProperty
und sicherstellen, dass es sowohl Gets als auch Sets verarbeitet.Beginnen wir mit bekommen.
Mein erster Versuch war, einfach ein neues UberProperty-Objekt zu erstellen und es zurückzugeben:
Ich stellte natürlich schnell fest, dass dies nicht funktioniert: Python bindet den Aufrufbaren niemals an das Objekt und ich benötige das Objekt, um die Funktion aufzurufen. Selbst das Erstellen des Dekorators in der Klasse funktioniert nicht, da wir jetzt zwar die Klasse haben, aber immer noch kein Objekt haben, mit dem wir arbeiten können.
Wir müssen hier also mehr tun können. Wir wissen, dass eine Methode nur einmal dargestellt werden muss. Lassen Sie uns also unseren Dekorateur behalten, aber ändern Sie sie,
UberProperty
um nur diemethod
Referenz zu speichern :Es ist auch nicht aufrufbar, so dass im Moment nichts funktioniert.
Wie vervollständigen wir das Bild? Was haben wir am Ende, wenn wir die Beispielklasse mit unserem neuen Dekorator erstellen:
In beiden Fällen erhalten wir das zurück,
UberProperty
was natürlich nicht aufrufbar ist, daher ist dies nicht sehr nützlich.Was wir brauchen, ist eine Möglichkeit,
UberProperty
die vom Dekorateur erstellte Instanz dynamisch zu binden, nachdem die Klasse an ein Objekt der Klasse erstellt wurde, bevor dieses Objekt zur Verwendung an diesen Benutzer zurückgegeben wurde. Ähm, ja, das ist ein__init__
Anruf, Alter.Schreiben wir auf, was unser Suchergebnis zuerst sein soll. Wir binden eine
UberProperty
an eine Instanz, daher ist es naheliegend, eine BoundUberProperty zurückzugeben. Hier behalten wir den Status für dasx
Attribut bei.Jetzt haben wir die Darstellung; Wie bringen Sie diese auf ein Objekt? Es gibt einige Ansätze, aber der am einfachsten zu erklärende verwendet nur die
__init__
Methode, um dieses Mapping durchzuführen. Zu dem Zeitpunkt__init__
, an dem unsere Dekorateure aufgerufen sind, müssen sie nur noch die Objekte durchsehen__dict__
und alle Attribute aktualisieren, bei denen der Wert des Attributs vom Typ istUberProperty
.Jetzt sind Uber-Eigenschaften cool und wir werden sie wahrscheinlich häufig verwenden wollen. Daher ist es sinnvoll, nur eine Basisklasse zu erstellen, die dies für alle Unterklassen tut. Ich denke, Sie wissen, wie die Basisklasse heißen wird.
Wir fügen dies hinzu, ändern unser Beispiel, um von zu erben
UberObject
, und ...Nach dem Ändern
x
zu sein:Wir können einen einfachen Test durchführen:
Und wir bekommen die Ausgabe, die wir wollten:
(Gee, ich arbeite spät.)
Beachten Sie, dass ich verwendet habe
getValue
,setValue
undclearValue
hier. Dies liegt daran, dass ich noch nicht die Mittel verknüpft habe, um diese automatisch zurückzugeben.Aber ich denke, dies ist ein guter Ort, um vorerst anzuhalten, weil ich müde werde. Sie können auch sehen, dass die von uns gewünschte Kernfunktionalität vorhanden ist. Der Rest ist Schaufensterdekoration. Wichtige Usability-Fensterdekoration, aber das kann warten, bis ich eine Änderung habe, um den Beitrag zu aktualisieren.
Ich werde das Beispiel im nächsten Beitrag beenden, indem ich folgende Dinge anspreche:
Wir müssen sicherstellen, dass UberObjects
__init__
immer von Unterklassen aufgerufen wird.Wir müssen sicherstellen, dass wir den allgemeinen Fall behandeln, in dem jemand eine Funktion auf etwas anderes 'aliasisiert', wie zum Beispiel:
Wir müssen
e.x
zurück ine.x.getValue()
der Standardeinstellung.e.x.getValue()
. (Dies zu tun ist offensichtlich, wenn Sie es noch nicht behoben haben.)Wir müssen die Einstellung unterstützen
e.x directly
, wie ine.x = <newvalue>
. Wir können dies auch in der übergeordneten Klasse tun, aber wir müssen unseren__init__
Code aktualisieren , um damit umzugehen.Schließlich werden wir parametrisierte Attribute hinzufügen. Es sollte ziemlich offensichtlich sein, wie wir das auch machen werden.
Hier ist der Code, wie er bisher existiert:
[1] Ich bin möglicherweise im Rückstand, ob dies noch der Fall ist.
quelle
return self.x or self.defaultX()
das ist gefährlicher Code. Was passiert wannself.x == 0
?__getitem__
Methode Sie überschrieben haben . Es wäre allerdings seltsam, da Sie dann völlig andere Standard-Python haben würden.Ich denke, beide haben ihren Platz. Ein Problem bei der Verwendung
@property
ist, dass es schwierig ist, das Verhalten von Gettern oder Setzern in Unterklassen mithilfe von Standardklassenmechanismen zu erweitern. Das Problem ist, dass die tatsächlichen Getter / Setter-Funktionen in der Eigenschaft versteckt sind.Sie können die Funktionen tatsächlich erreichen, z. B. mit
Sie können als
C.p.fget
und auf die Getter- und Setter-Funktionen zugreifenC.p.fset
, aber Sie können die normalen Methodenvererbungsfunktionen (z. B. Super) nicht einfach verwenden, um sie zu erweitern. Nach einigem Graben in die Feinheiten von super, man kann in der Tat auf diese Weise verwendet Super:Die Verwendung von super () ist jedoch ziemlich umständlich, da die Eigenschaft neu definiert werden muss und Sie den leicht kontraintuitiven Mechanismus super (cls, cls) verwenden müssen, um eine ungebundene Kopie von p zu erhalten.
quelle
Die Verwendung von Eigenschaften ist für mich intuitiver und passt besser in den meisten Code.
Vergleichen
vs.
ist für mich ganz offensichtlich was leichter zu lesen ist. Auch Eigenschaften ermöglichen private Variablen viel einfacher.
quelle
In den meisten Fällen würde ich lieber keine verwenden. Das Problem mit Eigenschaften ist, dass sie die Klasse weniger transparent machen. Dies ist insbesondere dann ein Problem, wenn Sie eine Ausnahme von einem Setter auslösen. Wenn Sie beispielsweise eine Account.email-Eigenschaft haben:
dann erwartet der Benutzer der Klasse nicht, dass das Zuweisen eines Werts zur Eigenschaft eine Ausnahme verursachen könnte:
Infolgedessen kann die Ausnahme unbehandelt bleiben und sich entweder zu hoch in der Aufrufkette ausbreiten, um ordnungsgemäß behandelt zu werden, oder dazu führen, dass dem Programmbenutzer ein sehr wenig hilfreiches Traceback angezeigt wird (was in der Welt von Python und Java leider zu häufig ist ).
Ich würde auch vermeiden, Getter und Setter zu verwenden:
Anstelle von Eigenschaften und Gettern / Setzern bevorzuge ich die komplexe Logik an genau definierten Stellen, z. B. in einer Validierungsmethode:
oder eine ähnliche Account.save-Methode.
Beachten Sie, dass ich nicht versuche zu sagen, dass es keine Fälle gibt, in denen Eigenschaften nützlich sind, sondern nur, dass Sie möglicherweise besser dran sind, wenn Sie Ihre Klassen so einfach und transparent gestalten können, dass Sie sie nicht benötigen.
quelle
validate()
Methode in einer Klasse haben. Eine Eigenschaft wird einfach verwendet, wenn Sie eine komplexe Logik hinter einer einfachenobj.x = y
Zuweisung haben und es an der Logik liegt.Ich habe das Gefühl, dass es bei Eigenschaften darum geht, dass Sie den Aufwand für das Schreiben von Gettern und Setzern nur dann erhalten, wenn Sie sie tatsächlich benötigen.
In der Java-Programmierkultur wird dringend empfohlen, niemals Zugriff auf Eigenschaften zu gewähren und stattdessen Getter und Setter zu verwenden, und nur diejenigen, die tatsächlich benötigt werden. Es ist etwas ausführlich, diese offensichtlichen Codeteile immer zu schreiben und zu beachten, dass sie in 70% der Fälle nie durch eine nicht triviale Logik ersetzt werden.
In Python kümmern sich die Leute tatsächlich um diese Art von Overhead, sodass Sie die folgende Praxis anwenden können:
@property
diese Option , um sie zu implementieren, ohne die Syntax des restlichen Codes zu ändern.quelle
Ich bin überrascht, dass niemand erwähnt hat, dass Eigenschaften gebundene Methoden einer Deskriptorklasse sind. Adam Donohue und NeilenMarais kommen in ihren Beiträgen genau auf diese Idee - dass Getter und Setter Funktionen sind und verwendet werden können, um:
Dies stellt eine intelligente Art und Weise zu verstecken Details der Implementierung und Code cruft wie reguläre Ausdrücke, Typ Abgüsse, try .. außer Blöcke, Behauptungen oder berechneten Werte.
Im Allgemeinen kann das Ausführen von CRUD für ein Objekt häufig recht banal sein. Betrachten Sie jedoch das Beispiel von Daten, die in einer relationalen Datenbank gespeichert werden. ORMs können Implementierungsdetails bestimmter SQL-Umgangssprachen in den Methoden verbergen, die an fget, fset, fdel gebunden sind, die in einer Eigenschaftsklasse definiert sind, die das Schreckliche verwaltet, wenn .. elif .. sonst Leitern, die im OO-Code so hässlich sind - das Einfache und elegant
self.variable = something
und vermeiden Sie die Details für den Entwickler mit dem ORM.Wenn man Eigenschaften nur als einen trostlosen Überrest einer Bondage- und Disziplin-Sprache (dh Java) betrachtet, fehlt ihnen der Punkt der Deskriptoren.
quelle
In komplexen Projekten bevorzuge ich schreibgeschützte Eigenschaften (oder Getter) mit expliziter Setter-Funktion:
In langlebigen Projekten dauert das Debuggen und Refactoring länger als das Schreiben des Codes selbst. Die Verwendung hat mehrere Nachteile,
@property.setter
die das Debuggen noch schwieriger machen:1) Python ermöglicht das Erstellen neuer Attribute für ein vorhandenes Objekt. Dies macht es sehr schwierig, einen folgenden Druckfehler zu verfolgen:
Wenn es sich bei Ihrem Objekt um einen komplizierten Algorithmus handelt, werden Sie einige Zeit damit verbringen, herauszufinden, warum es nicht konvergiert (beachten Sie ein zusätzliches 't' in der obigen Zeile).
2) Setter kann sich manchmal zu einer komplizierten und langsamen Methode entwickeln (z. B. das Schlagen einer Datenbank). Für einen anderen Entwickler wäre es ziemlich schwierig herauszufinden, warum die folgende Funktion sehr langsam ist. Möglicherweise verbringt er viel Zeit mit der Profilerstellung
do_something()
, während diesmy_object.my_attr = 4.
tatsächlich die Ursache für die Verlangsamung ist:quelle
Sowohl
@property
als auch traditionelle Getter und Setter haben ihre Vorteile. Dies hängt von Ihrem Anwendungsfall ab.Vorteile von
@property
Sie müssen die Schnittstelle nicht ändern, während Sie die Implementierung des Datenzugriffs ändern. Wenn Ihr Projekt klein ist, möchten Sie wahrscheinlich den direkten Attributzugriff verwenden, um auf ein Klassenmitglied zuzugreifen. Angenommen, Sie haben ein Objekt
foo
vom TypFoo
, das ein Mitglied hatnum
. Dann können Sie dieses Mitglied einfach mit bekommennum = foo.num
. Wenn Ihr Projekt wächst, haben Sie möglicherweise das Gefühl, dass der einfache Attributzugriff überprüft oder behoben werden muss. Dann können Sie das mit einem@property
innerhalb der Klasse tun . Die Datenzugriffsschnittstelle bleibt unverändert, sodass der Clientcode nicht geändert werden muss.Zitiert aus PEP-8 :
Die Verwendung
@property
für den Datenzugriff in Python wird als Pythonic angesehen :Es kann Ihre Selbstidentifikation als Python-Programmierer (nicht Java-Programmierer) stärken.
Es kann Ihrem Vorstellungsgespräch helfen, wenn Ihr Interviewer der Meinung ist, dass Getter und Setter im Java-Stil Anti-Patterns sind .
Vorteile traditioneller Getter und Setter
Herkömmliche Getter und Setter ermöglichen einen komplizierteren Datenzugriff als den einfachen Attributzugriff. Wenn Sie beispielsweise ein Klassenmitglied festlegen, benötigen Sie manchmal ein Flag, das angibt, wo Sie diesen Vorgang erzwingen möchten, auch wenn etwas nicht perfekt aussieht. Während es nicht offensichtlich ist, wie ein direkter Mitgliederzugriff wie erweitert
foo.num = num
werden kann, können Sie Ihren herkömmlichen Setter einfach um einen zusätzlichenforce
Parameter erweitern:Herkömmliche Getter und Setter machen deutlich, dass der Zugriff eines Klassenmitglieds über eine Methode erfolgt. Das heisst:
Was Sie als Ergebnis erhalten, stimmt möglicherweise nicht mit dem überein, was genau in dieser Klasse gespeichert ist.
Selbst wenn der Zugriff wie ein einfacher Attributzugriff aussieht, kann die Leistung stark davon abweichen.
Wenn Ihre Klassenbenutzer nicht erwarten, dass
@property
sich hinter jeder Attributzugriffsanweisung ein Versteck verbirgt, kann die explizite Darstellung solcher Dinge dazu beitragen, die Überraschungen Ihrer Klassenbenutzer zu minimieren.Wie von @NeilenMarais und in diesem Beitrag erwähnt , ist das Erweitern traditioneller Getter und Setter in Unterklassen einfacher als das Erweitern von Eigenschaften.
Traditionelle Getter und Setter sind seit langem in verschiedenen Sprachen weit verbreitet. Wenn Sie Leute mit unterschiedlichem Hintergrund in Ihrem Team haben, sehen sie vertrauter aus als
@property
. Wenn Ihr Projekt wächst und Sie möglicherweise von Python in eine andere Sprache migrieren müssen, die es nicht gibt@property
, würde die Verwendung herkömmlicher Getter und Setter die Migration reibungsloser gestalten.Vorsichtsmaßnahmen
Weder
@property
herkömmliche Getter und Setter machen das Klassenmitglied privat, selbst wenn Sie vor seinem Namen einen doppelten Unterstrich verwenden:quelle
Hier ist ein Auszug aus "Effektives Python: 90 spezifische Möglichkeiten, besseres Python zu schreiben" (Erstaunliches Buch. Ich kann es nur empfehlen).
quelle