Was ist die pythonische Art, Getter und Setter zu verwenden?

340

Ich mache es wie:

def set_property(property,value):  
def get_property(property):  

oder

object.property = value  
value = object.property

Ich bin neu in Python, daher erforsche ich immer noch die Syntax und möchte einige Ratschläge dazu.

Jorge Guberte
quelle

Antworten:

684

Versuchen Sie Folgendes: Python-Eigenschaft

Der Beispielcode lautet:

class C(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """I'm the 'x' property."""
        print("getter of x called")
        return self._x

    @x.setter
    def x(self, value):
        print("setter of x called")
        self._x = value

    @x.deleter
    def x(self):
        print("deleter of x called")
        del self._x


c = C()
c.x = 'foo'  # setter called
foo = c.x    # getter called
del c.x      # deleter called
Grissiom
quelle
2
Wird der Setter für x im Initialisierer aufgerufen, wenn _x instanziiert wird?
Casey
7
@Casey: Nein. Verweise auf ._x(was keine Eigenschaft, sondern nur ein einfaches Attribut ist) umgehen die propertyUmhüllung. Nur Referenzen zum .xDurchgehen der property.
ShadowRanger
272

Was ist die pythonische Art, Getter und Setter zu verwenden?

Die "pythonische" Methode besteht nicht darin, "Getter" und "Setter" zu verwenden, sondern einfache Attribute zu verwenden, wie die Frage zeigt, und sie delzu löschen (aber die Namen werden geändert, um die unschuldigen ... Eingebauten zu schützen):

value = 'something'

obj.attribute = value  
value = obj.attribute
del obj.attribute

Wenn Sie später die Einstellung ändern und abrufen möchten, können Sie dies tun, ohne den Benutzercode ändern zu müssen, indem Sie den propertyDekorator verwenden:

class Obj:
    """property demo"""
    #
    @property            # first decorate the getter method
    def attribute(self): # This getter method name is *the* name
        return self._attribute
    #
    @attribute.setter    # the property decorates with `.setter` now
    def attribute(self, value):   # name, e.g. "attribute", is the same
        self._attribute = value   # the "value" name isn't special
    #
    @attribute.deleter     # decorate with `.deleter`
    def attribute(self):   # again, the method name is the same
        del self._attribute

(Jede Dekorationsverwendung kopiert und aktualisiert das vorherige Eigenschaftsobjekt. Beachten Sie daher, dass Sie für jede Funktion / Methode zum Festlegen, Abrufen und Löschen denselben Namen verwenden sollten.

Nachdem Sie das oben Gesagte definiert haben, ist die ursprüngliche Einstellung, das Abrufen und Löschen von Code dieselbe:

obj = Obj()
obj.attribute = value  
the_value = obj.attribute
del obj.attribute

Sie sollten dies vermeiden:

def set_property(property,value):  
def get_property(property):  

Erstens funktioniert das oben Gesagte nicht, da Sie kein Argument für die Instanz angeben, auf die die Eigenschaft (normalerweise self) gesetzt werden würde. Dies wäre:

class Obj:

    def set_property(self, property, value): # don't do this
        ...
    def get_property(self, property):        # don't do this either
        ...

Zweitens dupliziert dies den Zweck von zwei speziellen Methoden, __setattr__und __getattr__.

Drittens haben wir auch die setattrund getattreingebauten Funktionen.

setattr(object, 'property_name', value)
getattr(object, 'property_name', default_value)  # default is optional

Der @propertyDekorateur dient zum Erstellen von Gettern und Setzern.

Beispielsweise könnten wir das Einstellungsverhalten ändern, um den festgelegten Wert einzuschränken:

class Protective(object):

    @property
    def protected_value(self):
        return self._protected_value

    @protected_value.setter
    def protected_value(self, value):
        if acceptable(value): # e.g. type or range check
            self._protected_value = value

Im Allgemeinen möchten wir die Verwendung vermeiden propertyund nur direkte Attribute verwenden.

Dies wird von Benutzern von Python erwartet. Nach der Regel der geringsten Überraschung sollten Sie versuchen, Ihren Benutzern das zu geben, was sie erwarten, es sei denn, Sie haben einen sehr zwingenden Grund für das Gegenteil.

Demonstration

Angenommen, das geschützte Attribut unseres Objekts muss eine Ganzzahl zwischen 0 und einschließlich 100 sein und das Löschen verhindern, mit entsprechenden Meldungen, um den Benutzer über die ordnungsgemäße Verwendung zu informieren:

class Protective(object):
    """protected property demo"""
    #
    def __init__(self, start_protected_value=0):
        self.protected_value = start_protected_value
    # 
    @property
    def protected_value(self):
        return self._protected_value
    #
    @protected_value.setter
    def protected_value(self, value):
        if value != int(value):
            raise TypeError("protected_value must be an integer")
        if 0 <= value <= 100:
            self._protected_value = int(value)
        else:
            raise ValueError("protected_value must be " +
                             "between 0 and 100 inclusive")
    #
    @protected_value.deleter
    def protected_value(self):
        raise AttributeError("do not delete, protected_value can be set to 0")

(Beachten Sie, dass __init__sich self.protected_valuedie Eigenschaft auf die Eigenschaftsmethoden bezieht self._protected_value. Auf diese Weise wird __init__die Eigenschaft über die öffentliche API verwendet, um sicherzustellen, dass sie "geschützt" ist.)

Und Verwendung:

>>> p1 = Protective(3)
>>> p1.protected_value
3
>>> p1 = Protective(5.0)
>>> p1.protected_value
5
>>> p2 = Protective(-5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __init__
  File "<stdin>", line 15, in protected_value
ValueError: protectected_value must be between 0 and 100 inclusive
>>> p1.protected_value = 7.3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 17, in protected_value
TypeError: protected_value must be an integer
>>> p1.protected_value = 101
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 15, in protected_value
ValueError: protectected_value must be between 0 and 100 inclusive
>>> del p1.protected_value
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 18, in protected_value
AttributeError: do not delete, protected_value can be set to 0

Sind die Namen wichtig?

Ja, das tun sie . .setterund .deletermachen Sie Kopien des ursprünglichen Eigentums. Auf diese Weise können Unterklassen das Verhalten ordnungsgemäß ändern, ohne das Verhalten im übergeordneten Element zu ändern.

class Obj:
    """property demo"""
    #
    @property
    def get_only(self):
        return self._attribute
    #
    @get_only.setter
    def get_or_set(self, value):
        self._attribute = value
    #
    @get_or_set.deleter
    def get_set_or_delete(self):
        del self._attribute

Damit dies funktioniert, müssen Sie die entsprechenden Namen verwenden:

obj = Obj()
# obj.get_only = 'value' # would error
obj.get_or_set = 'value'  
obj.get_set_or_delete = 'new value'
the_value = obj.get_only
del obj.get_set_or_delete
# del obj.get_or_set # would error

Ich bin nicht sicher, wo dies nützlich wäre, aber der Anwendungsfall ist, wenn Sie eine Nur-Abrufen-, Festlegen- und / oder Löschen-Eigenschaft wünschen. Wahrscheinlich am besten bei semantisch derselben Eigenschaft mit demselben Namen bleiben.

Fazit

Beginnen Sie mit einfachen Attributen.

Wenn Sie später Funktionen zum Einstellen, Abrufen und Löschen benötigen, können Sie diese mit dem Eigenschaftendekorateur hinzufügen.

Vermeiden Sie Funktionen mit dem Namen set_...und get_...- dafür sind Eigenschaften gedacht.

Aaron Hall
quelle
Warum sollte das Schreiben eigener Setter und Getter vermieden werden, abgesehen vom Duplizieren der verfügbaren Funktionen? Ich verstehe, dass es vielleicht nicht der pythonische Weg ist, aber gibt es wirklich ernste Probleme, auf die man sonst stoßen könnte?
user1350191
4
In Ihrer Demo __init__bezieht sich die Methode auf, self.protected_valueaber der Getter und die Setter beziehen sich auf self._protected_value. Könnten Sie bitte erklären, wie das funktioniert? Ich habe Ihren Code getestet und er funktioniert wie er ist - dies ist also kein Tippfehler.
Codeforester
2
@codeforester Ich hatte gehofft, in meiner Antwort früher antworten zu können, aber bis ich kann, sollte dieser Kommentar ausreichen. Ich hoffe, Sie können sehen, dass es das Eigentum über die öffentliche API nutzt, um sicherzustellen, dass es "geschützt" ist. Es wäre nicht sinnvoll, es mit einem Eigentum zu "schützen" und dann stattdessen die nicht öffentliche API zu verwenden __init__, oder?
Aaron Hall
2
Ja, @AaronHall hat es jetzt verstanden. Ich wusste nicht, self.protected_value = start_protected_valuedass die Setter-Funktion tatsächlich aufgerufen wird. Ich dachte, es wäre eine Aufgabe.
Codeforester
1
imho das sollte die akzeptierte antwort sein, wenn ich richtig verstanden habe, nimmt python genau den entgegengesetzten punkt im vergleich zu zB java. Anstatt alles standardmäßig privat zu machen und zusätzlichen Code zu schreiben, wenn er öffentlich in Python benötigt wird, können Sie alles öffentlich machen und später Datenschutz hinzufügen
idclev 463035818
27
In [1]: class test(object):
    def __init__(self):
        self.pants = 'pants'
    @property
    def p(self):
        return self.pants
    @p.setter
    def p(self, value):
        self.pants = value * 2
   ....: 
In [2]: t = test()
In [3]: t.p
Out[3]: 'pants'
In [4]: t.p = 10
In [5]: t.p
Out[5]: 20
Autoplektisch
quelle
17

Mit @propertyund können @attribute.setterSie nicht nur die "pythonische" Methode verwenden, sondern auch die Gültigkeit von Attributen sowohl beim Erstellen des Objekts als auch beim Ändern überprüfen.

class Person(object):
    def __init__(self, p_name=None):
        self.name = p_name

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, new_name):
        if type(new_name) == str: #type checking for name property
            self._name = new_name
        else:
            raise Exception("Invalid value for name")

Auf diese Weise "verbergen" Sie das _nameAttribut tatsächlich vor Client-Entwicklern und führen auch Überprüfungen des Namenseigenschaftstyps durch. Beachten Sie, dass der Setter aufgerufen wird, wenn Sie diesen Ansatz auch während der Initiierung befolgen. Damit:

p = Person(12)

Wird dazu führen:

Exception: Invalid value for name

Aber:

>>>p = person('Mike')
>>>print(p.name)
Mike
>>>p.name = 'George'
>>>print(p.name)
George
>>>p.name = 2.3 # Causes an exception
Farzad Schwindel
quelle
16

Schauen Sie sich den @propertyDekorateur an .

Kevin Little
quelle
33
Dies ist so ziemlich eine reine Linkantwort.
Aaron Hall
7
Wie ist das eine vollständige Antwort? Ein Link ist keine Antwort.
Andy_A̷n̷d̷y̷
Ich denke, es ist eine gute Antwort, da in der Dokumentation klar angegeben ist, wie sie verwendet werden soll (und auf dem neuesten Stand bleibt, wenn sich die Python-Implementierung ändert, und das OP auf die Methode verweist, die der Antwortende vorschlägt. @ Jean-FrançoisCorbett hat dies klar angegeben 'wie' es ist eine vollständige Antwort.
HunnyBear
In jedem Fall fügt diese Antwort anderen Antworten nichts Nützliches hinzu und sollte im Idealfall gelöscht werden.
Georgy
5

Sie können Accessoren / Mutatoren (dh @attr.setterund @property) verwenden oder nicht, aber das Wichtigste ist , konsistent zu sein!

Wenn Sie verwenden, @propertyum einfach auf ein Attribut zuzugreifen, z

class myClass:
    def __init__(a):
        self._a = a

    @property
    def a(self):
        return self._a

Verwenden Sie es, um auf jedes * Attribut zuzugreifen ! Es wäre eine schlechte Praxis, auf einige Attribute zuzugreifen @propertyund einige andere Eigenschaften ohne einen Accessor öffentlich zu lassen (dh Namen ohne Unterstrich), z. B. nicht

class myClass:
    def __init__(a, b):
        self.a = a
        self.b = b

    @property
    def a(self):
        return self.a

Beachten Sie, dass self.bhier kein expliziter Accessor vorhanden ist, obwohl er öffentlich ist.

Ähnlich ist es mit Setter (oder Mutatoren ), fühlen Sie sich frei zu verwenden , @attribute.setteraber konsequent sein! Wenn Sie z

class myClass:
    def __init__(a, b):
        self.a = a
        self.b = b 

    @a.setter
    def a(self, value):
        return self.a = value

Es fällt mir schwer, Ihre Absicht zu erraten. Einerseits sagen Sie, dass beide aund böffentlich sind (kein führender Unterstrich in ihren Namen), so dass ich theoretisch auf beide zugreifen / mutieren (get / set) dürfen sollte. Aber dann geben Sie einen expliziten Mutator nur für an a, der mir sagt, dass ich vielleicht nicht in der Lage sein sollte, zu setzen b. Da Sie einen expliziten Mutator angegeben haben, bin ich mir nicht sicher, ob das Fehlen von explizitem accessor ( @property) bedeutet, dass ich nicht auf eine dieser Variablen zugreifen kann oder dass Sie nur sparsam damit umgehen @property.

* Die Ausnahme ist, wenn Sie einige Variablen explizit zugänglich oder veränderbar machen möchten, aber nicht beide, oder wenn Sie beim Zugriff auf oder bei der Mutation eines Attributs eine zusätzliche Logik ausführen möchten. Dies ist, wenn ich persönlich benutze @propertyund @attribute.setter(ansonsten keine expliziten Acessoren / Mutatoren für öffentliche Attribute).

Zuletzt Vorschläge für PEP8 und Google Style Guide:

PEP8, Designing for Inheritance, sagt:

Für einfache öffentliche Datenattribute ist es am besten, nur den Attributnamen ohne komplizierte Accessor / Mutator-Methoden verfügbar zu machen . Beachten Sie, dass Python einen einfachen Weg zur zukünftigen Verbesserung bietet, falls Sie feststellen, dass ein einfaches Datenattribut das Funktionsverhalten verbessern muss. Verwenden Sie in diesem Fall Eigenschaften, um die funktionale Implementierung hinter der einfachen Datenattributzugriffssyntax zu verbergen.

Auf der anderen Seite lautet die Empfehlung gemäß den Python-Sprachregeln / -eigenschaften des Google Style Guide :

Verwenden Sie Eigenschaften in neuem Code, um auf Daten zuzugreifen oder diese festzulegen, für die Sie normalerweise einfache, einfache Accessor- oder Setter-Methoden verwendet hätten. Eigenschaften sollten mit dem @propertyDekorateur erstellt werden.

Die Vorteile dieses Ansatzes:

Die Lesbarkeit wird verbessert, indem explizite get- und set-Methodenaufrufe für einen einfachen Attributzugriff entfallen. Ermöglicht faulen Berechnungen. Betrachtet die pythonische Methode zur Pflege der Schnittstelle einer Klasse. In Bezug auf die Leistung umgeht das Zulassen von Eigenschaften das Erfordernis trivialer Zugriffsmethoden, wenn ein direkter Variablenzugriff sinnvoll ist. Auf diese Weise können auch in Zukunft Zugriffsmethoden hinzugefügt werden, ohne die Schnittstelle zu beschädigen.

und Nachteile:

Muss von objectPython 2 erben . Kann Nebenwirkungen verbergen, ähnlich wie das Überladen von Operatoren. Kann für Unterklassen verwirrend sein.

Tomasz Bartkowiak
quelle
1
Ich bin absolut anderer Meinung. Wenn ich 15 Attribute für mein Objekt habe und möchte, dass eines berechnet wird @property, @propertyscheint es eine schlechte Entscheidung zu sein , den Rest auch zu verwenden .
Quelklef
Stimmen Sie zu, aber nur, wenn dieses bestimmte Attribut etwas Bestimmtes enthält, das Sie benötigen @property(z. B. Ausführen einer speziellen Logik, bevor Sie ein Attribut zurückgeben). Warum würden Sie sonst ein Attribut mit @properyund nicht mit anderen dekorieren ?
Tomasz Bartkowiak
@Quelklef siehe die Nebenbemerkung im Beitrag (mit Sternchen markiert).
Tomasz Bartkowiak
Nun ... Wenn Sie eines der in der Nebenbemerkung genannten Dinge nicht tun, sollten Sie es zunächst nicht verwenden @property, oder? Wenn Ihr Getter ist return this._xund Ihr Setter ist this._x = new_x, dann ist die Verwendung @propertyüberhaupt etwas albern.
Quelklef
1
Hmm vielleicht. Ich persönlich würde sagen, dass es nicht in Ordnung ist - es ist völlig überflüssig. Aber ich kann sehen, woher du kommst. Ich glaube, ich habe Ihren Beitrag gerade so gelesen, dass "das Wichtigste bei der Verwendung @propertydie Konsistenz ist".
Quelklef
-1

Sie können die magischen Methoden __getattribute__und verwenden __setattr__.

class MyClass:
    def __init__(self, attrvalue):
        self.myattr = attrvalue
    def __getattribute__(self, attr):
        if attr == "myattr":
            #Getter for myattr
    def __setattr__(self, attr):
        if attr == "myattr":
            #Setter for myattr

Sei dir dessen bewusst __getattr__und __getattribute__bin nicht dasselbe. __getattr__wird nur aufgerufen, wenn das Attribut nicht gefunden wird.

Pika der Zauberer der Wale
quelle