Wann und wie wird die integrierte Funktionseigenschaft () in Python verwendet?

74

Es scheint mir, dass property () bis auf ein wenig syntaktischen Zucker nichts Gutes tut.

Sicher, es ist schön zu können , schreiben , a.b=2statt a.setB(2), aber die Tatsache versteckt , dass ab = 2 nicht eine einfache Zuordnung sieht aus wie ein Rezept für Probleme ist, sei es, weil einige unerwartete Ergebnis kann passieren, wie a.b=2tatsächlich verursacht a.bwerden 1. Oder es wird eine Ausnahme ausgelöst. Oder ein Leistungsproblem. Oder einfach nur verwirrend sein.

Können Sie mir ein konkretes Beispiel für eine gute Verwendung geben? (es zu zählen, um problematischen Code zu patchen, zählt nicht ;-)

Olamundo
quelle
12
Du hast es falsch verstanden. .setB () ist ein Patch für Sprachen, die keine realen Eigenschaften haben. Python, C # und einige andere haben Eigenschaften, und die Lösung ist dieselbe.
Liori
5
Ich habe gesehen, dass Java-Leute eine schlimmere Sache sind, nämlich Getter und Setter für jedes Attribut zu schreiben - nur für den Fall, weil es ein Schmerz ist, alles später umzugestalten.
John La Rooy
4
@gnibbler, in Sprachen, die keine Eigenschaften unterstützen (oder zumindest etwas äquivalente Möglichkeiten wie Pythons __getattr__/ __setattr__, die weit vor den Eigenschaften liegen), gibt es nicht viel mehr "Java-Leute" (in einer dieser Sprachen, einschließlich C ++! -) kann möglicherweise tun, um die Einkapselung zu erhalten.
Alex Martelli

Antworten:

138

In Sprachen wie Java, die auf Getter und Setter angewiesen sind, wird von ihnen nicht erwartet, dass sie etwas anderes tun als das, was sie sagen. Es wäre erstaunlich, wenn x.getB()sie etwas anderes tun würden, als den aktuellen Wert des logischen Attributs zurückzugeben b, oder wenn x.setB(2)sie etwas anderes tun würden Für die x.getB()Rückgabe ist ein geringer interner Arbeitsaufwand erforderlich 2.

Es gibt jedoch keine sprachlich auferlegten Garantien für dieses erwartete Verhalten, dh vom Compiler erzwungene Einschränkungen für den Methodenkörper, dessen Namen mit getoder setvielmehr dem gesunden Menschenverstand, den sozialen Konventionen, "Styleguides" und dem Testen überlassen bleiben .

Das Verhalten von x.bZugriffen und Zuweisungen wie x.b = 2in Sprachen, die Eigenschaften haben (eine Reihe von Sprachen, die Python enthalten, aber nicht darauf beschränkt sind), ist genau das gleiche wie für Getter- und Setter-Methoden in z. B. Java: dieselben Erwartungen, der gleiche Mangel an sprachlich erzwungenen Garantien.

Der erste Gewinn für Eigenschaften ist die Syntax und Lesbarkeit. Schreiben müssen, z.

x.setB(x.getB() + 1)

anstelle des Offensichtlichen

x.b += 1

schreit nach Rache an den Göttern. In Sprachen, die Eigenschaften unterstützen, gibt es absolut keinen guten Grund, Benutzer der Klasse zu zwingen, die Drehungen einer solchen byzantinischen Boilerplate zu durchlaufen, was die Lesbarkeit ihres Codes ohne jeglichen Vorteil beeinträchtigt.

Insbesondere in Python gibt es einen weiteren großen Vorteil bei der Verwendung von Eigenschaften (oder anderen Deskriptoren) anstelle von Gettern und Setzern: Wenn Sie Ihre Klasse so reorganisieren, dass der zugrunde liegende Setter und Getter nicht mehr benötigt werden, können Sie dies (ohne die Klassen zu beschädigen) veröffentlichte API) eliminieren einfach diese Methoden und die Eigenschaft, die auf ihnen beruht, und machen bein normales "gespeichertes" Attribut der xKlasse von 'anstatt eines "logischen" Attributs , das erhalten und rechnerisch festgelegt wird.

In Python ist es eine wichtige Optimierung, Dinge direkt (wenn möglich) statt über Methoden auszuführen. Durch die systematische Verwendung von Eigenschaften können Sie diese Optimierung durchführen, wann immer dies möglich ist (wobei "normale gespeicherte Attribute" immer direkt verfügbar gemacht werden und nur solche, die beim Zugriff berechnet werden müssen und / oder Einstellung über Methoden und Eigenschaften).

Wenn Sie also Getter und Setter anstelle von Eigenschaften verwenden, ohne die Lesbarkeit des Codes Ihrer Benutzer zu beeinträchtigen, verschwenden Sie auch unentgeltlich Maschinenzyklen (und die Energie, die während dieser Zyklen an ihren Computer fließt ;-), wiederum ohne guten Grund was auch immer.

Ihr einziges Argument gegen Eigenschaften ist z. B., dass "ein externer Benutzer normalerweise keine Nebenwirkungen aufgrund einer Zuweisung erwarten würde"; Sie vermissen jedoch die Tatsache, dass derselbe Benutzer (in einer Sprache wie Java, in der Getter und Setter allgegenwärtig sind) keine (beobachtbaren) "Nebenwirkungen" erwarten würde, wenn er einen Setter aufruft (und noch weniger für einen Getter) ;-). Es sind vernünftige Erwartungen, und es liegt an Ihnen als Klassenautor, zu versuchen, sie zu berücksichtigen - ob Ihr Setter und Getter direkt oder über eine Immobilie verwendet werden, spielt keine Rolle. Wenn Sie Methoden mit wichtigen beobachtbare Nebenwirkungen haben, sie nicht nennen sie getThis, setThatund sie nicht über Eigenschaften verwenden.

Die Beschwerde , dass die Eigenschaften „die Umsetzung verstecken“ ist völlig unberechtigt: Die meisten alle des OOP ist über Information Hiding Umsetzung - macht eine Klasse für eine logische Schnittstelle zur Außenwelt zu präsentieren und es intern als beste Umsetzung es kann. Getter und Setter sind genau wie Eigenschaften Werkzeuge für dieses Ziel. Eigenschaften machen es einfach besser (in Sprachen, die sie unterstützen ;-).

Alex Martelli
quelle
4
Schauen Sie sich auch die Überladung von C ++ - Operatoren an. Es gilt das gleiche Argument "Ein externer Benutzer würde nicht erwarten ...". In einigen Fällen möchten Sie nicht, dass der Benutzer es weiß. In diesen Fällen liegt es an Ihnen, sicherzustellen, dass Sie den Benutzer nicht überraschen.
Oren S
4
@Oren, guter Punkt, und Pythons Operatorüberladung ähnelt der von C ++ (Zuweisung kann nicht überladen werden, da es sich nicht um einen Operator usw. handelt, aber das allgemeine Konzept ist ähnlich). Es liegt an der Höflichkeit, Disziplin und dem gesunden Menschenverstand des Codierers, zu vermeiden, z. B. __add__sich selbst zu ändern und / oder etwas zu tun, das nichts mit Addition zu tun hat - das bedeutet nicht, dass eine Überladung des Bedieners eine schlechte Sache ist, wenn sie geschmackvoll und vernünftig verwendet wird (obwohl Javas Designer stimme nicht zu, da sie es absichtlich aus ihrer Sprache herausgelassen haben! -).
Alex Martelli
"... Sie können (ohne die veröffentlichte API der Klasse zu beschädigen) einfach diese Methoden und die darauf basierende Eigenschaft entfernen, indem Sie ein normales" gespeichertes "Attribut der Klasse von x anstelle eines" logischen "Attributs erhalten, das erhalten und rechnerisch festgelegt wird." - ??? Ich verstehe das nicht. Ich verstehe, dass sich die öffentliche "Schnittstelle", die von der Eigenschaft bereitgestellt wird, nicht ändern muss, wenn Sie die zugrunde liegenden internen Attribute ändern, aus denen die Eigenschaft besteht. Dies gilt jedoch auch für Getter / Setter. Beide sind nur eine Abstraktionsschicht über einem Wert, der objektintern sein sollte. Was versuchst du hier zu sagen?
Adam Parkin
8
@Adam Parkin Wenn ein Client-Programm 'GetTaxRate ()' aufruft, können Sie die Methode 'GetTaxRate' nicht mehr entfernen, ohne die Vergleichbarkeit rückwärts zu beeinträchtigen. Sie können jedoch eine '@property' entfernen, ohne die Abwärtskompatibilität zu beeinträchtigen, da der direkte Zugriff und '@property' dieselbe Syntax verwenden.
AlexLordThorsen
34

Die Idee ist, dass Sie vermeiden müssen, Getter und Setter schreiben zu müssen, bis Sie sie tatsächlich benötigen.

Um zu beginnen, schreiben Sie:

class MyClass(object):
    def __init__(self):
        self.myval = 4

Natürlich können Sie jetzt schreiben myobj.myval = 5.

Aber später entscheiden Sie, dass Sie einen Setter brauchen, da Sie gleichzeitig etwas Kluges tun möchten. Aber Sie möchten nicht den gesamten Code ändern müssen, der Ihre Klasse verwendet - also wickeln Sie den Setter in den @propertyDekorator ein, und alles funktioniert einfach.

Daniel Roseman
quelle
3
Ihr Beispiel ist genau das, was ich mit "Verwenden zum Patchen von problematischem Code" gemeint habe. Vielleicht ist "Problematisch" hier nicht das richtige Wort, aber es ist ein Patch-IMO.
Olamundo
Ich habe @olamundo zurückgedacht: Zu oft habe ich gesehen, dass Variablen @propertydem Setter / Getter Logik hinzufügten und die Kompatibilität nicht beeinträchtigten. Dies führt absichtlich zu @propertyNebenwirkungen und ist für zukünftige Programmierer mit demselben Code verwirrend.
Lorenzo Belli
15

Aber die Tatsache zu verbergen, dass ab = 2 keine einfache Aufgabe ist, scheint ein Rezept für Ärger zu sein

Sie verstecken diese Tatsache jedoch nicht; Diese Tatsache war von Anfang an nie da. Dies ist Python - eine Hochsprache; keine Montage. Nur wenige der darin enthaltenen "einfachen" Anweisungen beschränken sich auf einzelne CPU-Anweisungen. Einfachheit in eine Aufgabe einzulesen bedeutet, Dinge zu lesen, die nicht da sind.

Wenn Sie xb = c sagen, sollten Sie wahrscheinlich nur denken, dass "was auch immer gerade passiert ist, xb sollte jetzt c sein".

Lee B.
quelle
Ich stimme dem zu, was Sie gesagt haben, aber ich betrachte es immer noch als "Verstecken" des Innenlebens in dem Sinne, dass ein externer Benutzer normalerweise keine Nebenwirkungen aufgrund einer Zuweisung erwarten würde.
Olamundo
5
Das ist wahr, noam. Auf der anderen Seite dreht sich bei OOP alles um Objekte, die Black Boxes sind, mit Ausnahme der Schnittstellen, die sie darstellen. Man sollte also wahrscheinlich annehmen, dass unter der Oberfläche seltsame, magische Dinge vor sich gehen;)
Lee B
2
ab = 2 ist kaum weniger eine Zuweisung als x = 2. Wenn sie unterschiedlich implementiert sind, besteht der Sinn von OOP darin, Implementierungsdetails zu verbergen. Wenn ab = 2 etwas auf 490 setzt, dann ist es ein böser Nebeneffekt, der von a.setB (2) sowieso nicht gelöst werden würde. Wenn Sie implizit denken, dass ab = 2 eine sehr schnelle Operation sein sollte, wenn Sie es sehen, dann sollten Sie erkennen, dass Sie sich in Python befinden und bereits mindestens eine Größenordnung langsamer als C sind.
Ahnungslos
5

Ein grundlegender Grund ist wirklich einfach, dass es besser aussieht. Es ist mehr pythonisch. Besonders für Bibliotheken. etwas.getValue () sieht weniger gut aus als etwas.Wert

In plone (einem ziemlich großen CMS) hatten Sie früher document.setTitle (), das viele Dinge wie das Speichern des Werts, das erneute Indizieren und so weiter erledigt. Nur document.title = 'etwas' zu tun ist schöner. Sie wissen, dass hinter den Kulissen sowieso viel passiert.

Reinout van Rees
quelle
3

Sie haben Recht, es ist nur syntaktischer Zucker. Abhängig von Ihrer Definition des problematischen Codes kann es sein, dass es keine gute Verwendung gibt.

Bedenken Sie, dass Sie eine Klasse Foo haben, die in Ihrer Anwendung weit verbreitet ist. Jetzt ist diese Anwendung ziemlich groß geworden und es ist eine Webanwendung, die sehr beliebt geworden ist.

Sie erkennen, dass Foo einen Engpass verursacht. Vielleicht ist es möglich, Foo etwas Caching hinzuzufügen, um es zu beschleunigen. Mit den Eigenschaften können Sie dies tun, ohne Code oder Tests außerhalb von Foo zu ändern.

Ja, das ist natürlich problematischer Code, aber Sie haben gerade eine Menge Geld gespart, um ihn schnell zu beheben.

Was ist, wenn sich Foo in einer Bibliothek befindet, für die Sie Hunderte oder Tausende von Benutzern haben? Nun, Sie haben es sich erspart, ihnen sagen zu müssen, dass sie einen teuren Refactor durchführen sollen, wenn sie auf die neueste Version von Foo upgraden.

Die Versionshinweise enthalten anstelle einer Anleitung zur Absatzportierung ein Lineitem zu Foo.

Erfahrene Python-Programmierer erwarten nicht viel von a.b=2anderen als a.b==2, aber sie wissen, dass selbst das möglicherweise nicht stimmt. Was in der Klasse passiert, ist ein eigenes Geschäft.

John La Rooy
quelle
2

Hier ist ein altes Beispiel von mir. Ich habe eine C-Bibliothek mit Funktionen wie "void dt_setcharge (int atom_handle, int new_charge)" und "int dt_getcharge (int atom_handle)" eingeschlossen. Ich wollte auf Python-Ebene "atom.charge = atom.charge + 1" machen.

Der Dekorateur "Eigentum" macht das einfach. Etwas wie:

class Atom(object):
    def __init__(self, handle):
        self.handle = handle
    def _get_charge(self):
        return dt_getcharge(self.handle)
    def _set_charge(self, charge):
        dt_setcharge(self.handle, charge)
    charge = property(_get_charge, _set_charge)

Als ich dieses Paket vor 10 Jahren schrieb, musste ich __getattr__ und __setattr__ verwenden, was es möglich machte, aber die Implementierung war viel fehleranfälliger.

class Atom:
    def __init__(self, handle):
        self.handle = handle
    def __getattr__(self, name):
        if name == "charge":
            return dt_getcharge(self.handle)
        raise AttributeError(name)
    def __setattr__(self, name, value):
        if name == "charge":
            dt_setcharge(self.handle, value)
        else:
            self.__dict__[name] = value
Andrew Dalke
quelle
1

Getter und Setter werden für viele Zwecke benötigt und sind sehr nützlich, da sie für den Code transparent sind. Wenn das Objekt Something die Eigenschaft height hat, weisen Sie einen Wert als Something.height = 10 zu. Wenn die Höhe jedoch einen Getter und einen Setter hat, können Sie zu dem Zeitpunkt, zu dem Sie diesen Wert zuweisen, viele Dinge in den Prozeduren tun, z. B. das Validieren eines Min oder Max Wert, wie das Auslösen eines Ereignisses, weil sich die Höhe geändert hat, und das automatische Festlegen anderer Werte in Abhängigkeit vom neuen Höhenwert. Dies kann alles sein, was zum Zeitpunkt der Zuweisung von Something.height-Werten auftreten kann. Denken Sie daran, dass Sie sie in Ihrem Code nicht aufrufen müssen. Sie werden automatisch ausgeführt, sobald Sie den Eigenschaftswert lesen oder schreiben. In gewisser Weise sind sie wie Ereignisprozeduren, wenn der Wert der Eigenschaft X den Wert ändert und wenn der Wert der Eigenschaft X gelesen wird.

Avenida Gez
quelle