Beispiel aus der Praxis zur Verwendung der Eigenschaftsfunktion in Python?

142

Ich interessiere mich für die Verwendung @propertyin Python. Ich habe die Python-Dokumente gelesen und das Beispiel dort ist meiner Meinung nach nur ein Spielzeugcode:

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

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

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

Ich weiß nicht, welchen Nutzen ich aus dem Einwickeln des _xmit dem Immobiliendekorateur gefüllten Objektors ziehen kann. Warum nicht einfach implementieren als:

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

Ich denke, die Eigenschaftsfunktion kann in einigen Situationen nützlich sein. Aber wenn? Könnte mir bitte jemand einige Beispiele aus der Praxis geben?

Vielen Dank.

xiao 啸
quelle
9
Dies ist die beste und sauberste Erklärung, die ich über Immobiliendekorateur gefunden habe [hier klicken ]
Sumudu
2
@Anubis im letzten Beispiel in dem von Ihnen angegebenen Link hat die Einstellung c = Celsius (-500) keinen ValueError ausgelöst, der meiner Meinung nach nicht das beabsichtigte Ergebnis erzielt.
Sajuuk
Stimmen Sie mit @Anubis überein. Es ist hier korrekt implementiert: python-course.eu/python3_properties.php
anon01

Antworten:

91

Andere Beispiele wären die Validierung / Filterung der Mengenattribute (die sie in Grenzen halten oder akzeptabel sind) und die verzögerte Bewertung komplexer oder sich schnell ändernder Begriffe.

Komplexe Berechnung hinter einem Attribut versteckt:

class PDB_Calculator(object):
    ...
    @property
    def protein_folding_angle(self):
        # number crunching, remote server calls, etc
        # all results in an angle set in 'some_angle'
        # It could also reference a cache, remote or otherwise,
        # that holds the latest value for this angle
        return some_angle

>>> f = PDB_Calculator()
>>> angle = f.protein_folding_angle
>>> angle
44.33276

Validierung:

class Pedometer(object)
    ...
    @property
    def stride_length(self):
        return self._stride_length

    @stride_length.setter
    def stride_length(self, value):
        if value > 10:
            raise ValueError("This pedometer is based on the human stride - a stride length above 10m is not supported")
        else:
            self._stride_length = value
benosteen
quelle
1
Ich mag das PDB_Calculator-Beispiel - komplizierte Dinge werden abstrahiert, das Ganze funktioniert und der Benutzer kann die Einfachheit genießen!
Adam Kurkiewicz
2
möglicherweise sind dies vom professionellen Standpunkt aus sehr gute Beispiele. Aber als Noobie finde ich diese Beispiele ziemlich ineffektiv. mein schlechtes ... :(
kmonsoor
77

Ein einfacher Anwendungsfall besteht darin, ein schreibgeschütztes Instanzattribut festzulegen, da das Führen eines Variablennamens mit einem Unterstrich _xin Python normalerweise bedeutet, dass es privat ist (interne Verwendung), aber manchmal möchten wir das Instanzattribut lesen und nicht schreiben können es so können wir propertydafür verwenden:

>>> class C(object):

        def __init__(self, x):
            self._x = x

        @property
        def x(self):
            return self._x

>>> c = C(1)
>>> c.x
1
>>> c.x = 2
AttributeError        Traceback (most recent call last)

AttributeError: can't set attribute
Mouad
quelle
6
Man kann noch einstellen c._x, wenn der Benutzer will. Python hat eigentlich keine echten privaten Attribute.
20

Schauen Sie sich diesen Artikel für eine sehr praktische Anwendung an. Kurz gesagt, es wird erklärt, wie Sie in Python normalerweise die explizite Getter / Setter-Methode loswerden können, da Sie sie propertyfür eine nahtlose Implementierung verwenden können, wenn Sie sie irgendwann benötigen .

Eli Bendersky
quelle
15

Eine Sache, für die ich es verwendet habe, ist das Zwischenspeichern von langsam nachzuschauenden, aber unveränderlichen Werten, die in einer Datenbank gespeichert sind. Dies verallgemeinert sich auf jede Situation, in der Ihre Attribute eine Berechnung oder eine andere lange Operation erfordern (z. B. Datenbankprüfung, Netzwerkkommunikation), die Sie nur bei Bedarf ausführen möchten.

class Model(object):

  def get_a(self):
    if not hasattr(self, "_a"):
      self._a = self.db.lookup("a")
    return self._a

  a = property(get_a)

Dies geschah in einer Web-App, in der für eine bestimmte Seitenansicht möglicherweise nur ein bestimmtes Attribut dieser Art erforderlich ist, die zugrunde liegenden Objekte selbst jedoch möglicherweise mehrere solcher Attribute aufweisen. Eine Initialisierung aller Objekte bei der Erstellung wäre verschwenderisch, und die Eigenschaften ermöglichen es mir, flexibel zu sein Attribute sind faul und welche nicht.

detly
quelle
1
Kannst du das nicht benutzen @cached_property?
Adarsh
@adarsh ​​- Klingt interessant. Wo ist das?
Detly
Ich habe es benutzt, aber ich habe vergessen, dass es nicht eingebaut war, aber Sie können es damit verwenden, pypi.python.org/pypi/cached-property/0.1.5
adarsh
2
Interessant. Ich denke, es wurde zuerst nach dieser Antwort veröffentlicht, aber jeder, der dies liest, sollte es wahrscheinlich stattdessen verwenden.
Detly
10

Beim Lesen der Antworten und Kommentare scheint das Hauptthema zu sein, dass den Antworten ein einfaches, aber nützliches Beispiel fehlt. Ich habe hier eine sehr einfache aufgenommen, die die einfache Verwendung des @propertyDekorateurs demonstriert . Es ist eine Klasse, mit der ein Benutzer die Entfernungsmessung mit einer Vielzahl verschiedener Einheiten angeben und abrufen kann, z. B. in_feetoder in_metres.

class Distance(object):
    def __init__(self):
        # This private attribute will store the distance in metres
        # All units provided using setters will be converted before
        # being stored
        self._distance = 0.0

    @property
    def in_metres(self):
        return self._distance

    @in_metres.setter
    def in_metres(self, val):
        try:
            self._distance = float(val)
        except:
            raise ValueError("The input you have provided is not recognised "
                             "as a valid number")

    @property
    def in_feet(self):
        return self._distance * 3.2808399

    @in_feet.setter
    def in_feet(self, val):
        try:
            self._distance = float(val) / 3.2808399
        except:
            raise ValueError("The input you have provided is not recognised "
                             "as a valid number")

    @property
    def in_parsecs(self):
        return self._distance * 3.24078e-17

    @in_parsecs.setter
    def in_parsecs(self, val):
        try:
            self._distance = float(val) / 3.24078e-17
        except:
            raise ValueError("The input you have provided is not recognised "
                             "as a valid number")

Verwendung:

>>> distance = Distance()
>>> distance.in_metres = 1000.0
>>> distance.in_metres
1000.0
>>> distance.in_feet
3280.8399
>>> distance.in_parsecs
3.24078e-14
Mike
quelle
Für mich persönlich sind die besten Beispiele für Getter / Setter, den Leuten die Art von Änderungen zu zeigen, die Sie später vornehmen müssen, aber das dauert natürlich etwas länger.
dtc
7

Eigenschaft ist nur eine Abstraktion um ein Feld, mit der Sie mehr Kontrolle darüber haben, wie ein bestimmtes Feld manipuliert werden kann, und Middleware-Berechnungen durchführen können. Einige der Verwendungszwecke, die in den Sinn kommen, sind Validierung und vorherige Initialisierung sowie Zugriffsbeschränkungen

@property
def x(self):
    """I'm the 'x' property."""
    if self._x is None:
        self._x = Foo()

    return self._x
Stanislav Ageev
quelle
6

Ja, für das ursprünglich veröffentlichte Beispiel funktioniert die Eigenschaft genauso wie eine Instanzvariable 'x'.

Dies ist das Beste an Python-Eigenschaften. Von außen funktionieren sie genau wie Instanzvariablen! Damit können Sie Instanzvariablen von außerhalb der Klasse verwenden.

Dies bedeutet, dass Ihr erstes Beispiel tatsächlich eine Instanzvariable verwenden könnte. Wenn sich die Dinge ändern und Sie sich dann dazu entschließen, Ihre Implementierung zu ändern und eine Eigenschaft nützlich ist, ist die Schnittstelle zur Eigenschaft von Code außerhalb der Klasse immer noch dieselbe. Eine Änderung von einer Instanzvariablen zu einer Eigenschaft hat keine Auswirkungen auf Code außerhalb der Klasse.

In vielen anderen Sprachen und Programmierkursen wird angewiesen, dass ein Programmierer niemals Instanzvariablen verfügbar machen und stattdessen "Getter" und "Setter" für jeden Wert verwenden sollte, auf den von außerhalb der Klasse zugegriffen werden kann, selbst in dem in der Frage angegebenen einfachen Fall.

Code außerhalb der Klasse mit vielen Sprachen (z. B. Java)

object.get_i()
    #and
object.set_i(value)

#in place of (with python)
object.i
    #and 
object.i = value

Und bei der Implementierung der Klasse gibt es viele 'Getter' und 'Setter', die genau wie Ihr erstes Beispiel funktionieren: Replizieren Sie eine einfache Instanzvariable. Diese Getter und Setter sind erforderlich, da sich der gesamte Code außerhalb der Klasse ändern muss, wenn sich die Klassenimplementierung ändert. Mit Python-Eigenschaften kann Code außerhalb der Klasse jedoch mit Instanzvariablen identisch sein. Code außerhalb der Klasse muss also nicht geändert werden, wenn Sie eine Eigenschaft hinzufügen oder eine einfache Instanzvariable haben. So anders als die meisten objektorientierten Sprachen, für Ihr einfaches Beispiel Sie können das Instanz - Variable anstelle von ‚Getter‘ und ‚Setter‘ , die wirklich nicht benötigt werden, in dem Wissen sicher , dass , wenn Sie eine Immobilie in der Zukunft ändern, wird der Code unter Verwendung von Ihre Klasse muss sich nicht ändern.

Dies bedeutet, dass Sie nur dann Eigenschaften erstellen müssen, wenn ein komplexes Verhalten vorliegt. Für den sehr häufigen einfachen Fall, in dem, wie in der Frage beschrieben, lediglich eine einfache Instanzvariable erforderlich ist, können Sie einfach die Instanzvariable verwenden.

innov8
quelle
6

Ein weiteres nettes Merkmal von Eigenschaften gegenüber der Verwendung von Setzern und Gettern ist, dass Sie weiterhin OP = -Operatoren (z. B. + =, - =, * = usw.) für Ihre Attribute verwenden können, während Validierung, Zugriffskontrolle, Caching usw. beibehalten werden Die Setter und Getter würden liefern.

Wenn Sie beispielsweise die Klasse Personmit einem Setter setage(newage)und einem Getter geschrieben haben getage(), müssen Sie Folgendes schreiben, um das Alter zu erhöhen:

bob = Person('Robert', 25)
bob.setage(bob.getage() + 1)

aber wenn Sie ageeine Eigenschaft gemacht haben, könnten Sie das viel sauberere schreiben:

bob.age += 1
Quizdog
quelle
5

Die kurze Antwort auf Ihre Frage lautet, dass es in Ihrem Beispiel keinen Nutzen gibt. Sie sollten wahrscheinlich das Formular verwenden, das keine Eigenschaften enthält.

Der Grund, warum Eigenschaften vorhanden sind, besteht darin, dass Sie, wenn sich Ihr Code in Zukunft ändert und Sie plötzlich mehr mit Ihren Daten tun müssen: Werte zwischenspeichern, Zugriff schützen, externe Ressourcen abfragen ... Sie können Ihre Klasse einfach ändern, um Getter hinzuzufügen und Setter für die Daten, ohne die Schnittstelle zu ändern, sodass Sie nicht überall in Ihrem Code finden müssen, wo auf diese Daten zugegriffen wird, und dies auch ändern müssen.

SpoonMeiser
quelle
4

Was viele zunächst nicht bemerken, ist, dass Sie Ihre eigenen Unterklassen von Eigentum erstellen können. Dies hat sich als sehr nützlich erwiesen, um schreibgeschützte Objektattribute oder Attribute anzuzeigen, die Sie lesen und schreiben, aber nicht entfernen können. Es ist auch eine hervorragende Möglichkeit, Funktionen wie das Verfolgen von Änderungen an Objektfeldern zu verpacken.

class reader(property):
    def __init__(self, varname):
        _reader = lambda obj: getattr(obj, varname)
        super(reader, self).__init__(_reader)

class accessor(property):
    def __init__(self, varname, set_validation=None):
        _reader = lambda obj: getattr(obj, varname)
        def _writer(obj, value):
            if set_validation is not None:
               if set_validation(value):
                  setattr(obj, varname, value)
        super(accessor, self).__init__(_reader, _writer)

#example
class MyClass(object):
   def __init__(self):
     self._attr = None

   attr = reader('_attr')

quelle
Ich mag das. Lese ich das richtig, indem der Reader nur gelesen wird, während der Accessor ohne Löschfunktion gelesen / geschrieben wird? Wie würden Sie die Datenvalidierung hinzufügen? Ich bin ziemlich neu in Python, aber ich denke, es gibt wahrscheinlich eine Möglichkeit, der attr = reader('_attr')Zeile einen Rückruf oder eine Form der Vorprüfung hinzuzufügen attr = if self.__isValid(value): reader('_attr'). Vorschläge?
Gabe Spradlin
Es tut mir leid, dass ich gerade nach der Datenüberprüfung für eine schreibgeschützte Variable gefragt habe. Dies würde jedoch offensichtlich nur für den Setter-Teil der Accessor-Klasse gelten. Also wechseln Sie attr = reader('_attr')zu attr = accessor('_attr'). Danke
Gabe Spradlin
Sie haben Recht, wenn Sie eine Validierung wünschen, fügen Sie dem Init eine Funktion zum Validieren und Auslösen einer Ausnahme hinzu, wenn diese ungültig ist (oder was auch immer Sie möchten, einschließlich Nichtstun) . Ich habe das Obige mit einem möglichen Muster modifiziert. Der Validator sollte True | False zurückgeben, um zu bestimmen, ob die Menge auftritt oder nicht.