Python @property decorator
Table of Contents
In diesem Tutorial lernen Sie den Python @property Decorator kennen; eine pythonische Art, Getter und Setter in der objekt orientierten Programmierung zu verwenden.
Die Python-Programmierung bietet uns eine built-in @property
Decorator, der die Verwendung von Getter und Setter in der objektorientierten Programmierung wesentlich erleichtert.
Bevor ich ins Detail gehe, was @property
Decorator ist, lassen Sie uns zunächst eine Intuition dafür entwickeln, warum er überhaupt benötigt wird.
Class Without Getters and Setters
Nehmen wir an, dass wir uns entscheiden, eine Klasse zu erstellen, die die Temperatur in Grad Celsius speichert. Sie würde auch eine Methode implementieren, um die Temperatur in Grad Fahrenheit zu konvertieren. Eine Möglichkeit, dies zu tun, ist wie folgt:
class Celsius: def __init__(self, temperature = 0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32
Wir können aus dieser Klasse Objekte machen und die temperature
attribut nach Wunsch:
# Grundlegende Methode zum Setzen und Abrufen von Attributen in Python class Celsius: def __init__(self, temperature=0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32 # Ein neues Objekt erstellen human = Celsius() # Einstellen der Temperatur human.temperature = 37 # Holen Sie das Attribut Temperatur print(human.temperature) # Get the to_fahrenheit method print(human.to_fahrenheit())
Output
37 98.60000000000001
Die zusätzlichen Dezimalstellen bei der Konvertierung in Fahrenheit sind auf den Fließkomma-Arithmetikfehler zurückzuführen. Um mehr zu erfahren, besuchen Sie Python Floating Point Arithmetic Error.
Wann immer wir ein Objektattribut zuweisen oder abrufen, wie temperature
wie oben gezeigt, sucht Python es in der built-in __dict__
dictionary attribute.
>>> human.__dict__ {'temperature': 37}
Deshalb, man.temperature
wird intern zu man.__dict__['temperature']
.
Using Getters and Setters
Angenommen, wir wollen die Verwendbarkeit des Celsius Klasse, die oben definiert ist. Wir wissen, dass die Temperatur eines beliebigen Objekts nicht unter -273,15 Grad Celsius (Absoluter Nullpunkt in der Thermodynamik) erreichen kann
Lassen Sie uns unseren Code aktualisieren, um diese Werteinschränkung zu implementieren.
Eine offensichtliche Lösung für die obige Einschränkung ist das Ausblenden des Attributs temperature
(machen Sie es privat) und definieren Sie neue Getter- und Setter-Methoden, um es zu manipulieren. Dies kann wie folgt geschehen:
# Making Getters and Setter methods class Celsius: def __init__(self, temperature=0): self.set_temperature(temperature) def to_fahrenheit(self): return (self.get_temperature() * 1.8) + 32 # getter method def get_temperature(self): return self._temperature # setter method def set_temperature(self, value): if value < -273.15: raise ValueError("Temperature below -273.15 is not possible.") self._temperature = value
Wie wir sehen können, führt die obige Methode zwei neue get_temperature()
und set_temperature()
methods.
Außerdem, temperature
wurde ersetzt durch _temperature
. Ein Unterstrich _
am Anfang wird verwendet, um private Variablen in Python zu kennzeichnen.
Lassen Sie uns nun diese Implementierung verwenden:
# Making Getters and Setter methods class Celsius: def __init__(self, temperature=0): self.set_temperature(temperature) def to_fahrenheit(self): return (self.get_temperature() * 1.8) + 32 # getter method def get_temperature(self): return self._temperature # setter method def set_temperature(self, value): if value < -273.15: raise ValueError("Temperature below -273.15 is not possible.") self._temperature = value # Create a new object, set_temperature() internally called by __init__ human = Celsius(37) # Get the temperature attribute via a getter print(human.get_temperature()) # Get the to_fahrenheit method, get_temperature() called by the method itself print(human.to_fahrenheit()) # new constraint implementation human.set_temperature(-300) # Get the to_fahreheit method print(human.to_fahrenheit())
Output
37 98.60000000000001 Traceback (most recent call last): File "<string>", line 30, in <module> File "<string>", line 16, in set_temperature ValueError: Temperature below -273.15 is not possible.
Mit diesem Update wurde die neue Einschränkung erfolgreich umgesetzt. Wir dürfen die Temperatur nicht mehr unter -273,15 Grad Celsius einstellen.
Notiz
Die privaten Variablen gibt es in Python eigentlich nicht. Es gibt lediglich Normen, die zu beachten sind. Die Sprache selbst macht keine Einschränkungen.
>>> human._temperature = -300 >>> human.get_temperature() -300
Das größere Problem mit der obigen Aktualisierung ist jedoch, dass alle Programme, die unsere vorherige Klasse implementiert haben, ihren Code von obj.temperature
to obj.get_temperature()
und alle Ausdrücke wie obj.temperature = val
to obj.set_temperature(val)
.
Dieses Refactoring kann bei Hunderttausenden von Codezeilen zu Problemen führen.
All in all, our new update was not backwards compatible. This is where @property
comes to rescue.
The property Class
Ein pythonischer Weg, das obige Problem zu lösen, ist die Verwendung der property
Klasse. Hier sehen Sie, wie wir unseren Code aktualisieren können:
# using property class class Celsius: def __init__(self, temperature=0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32 # getter def get_temperature(self): print("Getting value...") return self._temperature # setter def set_temperature(self, value): print("Setting value...") if value < -273.15: raise ValueError("Temperature below -273.15 is not possible") self._temperature = value # creating a property object temperature = property(get_temperature, set_temperature)
Wir haben eine print()
Funktion innerhalb get_temperature()
und set_temperature()
um deutlich zu erkennen, dass sie ausgeführt werden.
Die letzte Zeile des Codes macht ein Eigenschaftsobjekt temperature
. Einfach ausgedrückt, fügt die Eigenschaft etwas Code hinzu (get_temperature
und set_temperature
) auf das Mitgliedsattribut zugreift (temperature
).
Lassen Sie uns diesen Update-Code verwenden:
# using property class class Celsius: def __init__(self, temperature=0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32 # getter def get_temperature(self): print("Getting value...") return self._temperature # setter def set_temperature(self, value): print("Setting value...") if value < -273.15: raise ValueError("Temperature below -273.15 is not possible") self._temperature = value # creating a property object temperature = property(get_temperature, set_temperature) human = Celsius(37) print(human.temperature) print(human.to_fahrenheit()) human.temperature = -300
Output
Setting value... Getting value... 37 Getting value... 98.60000000000001 Setting value... Traceback (most recent call last): File "<string>", line 31, in <module> File "<string>", line 18, in set_temperature ValueError: Temperature below -273 is not possible
Wie wir sehen können, kann jeder Code, der den Wert von temperature
ruft automatisch an get_temperature()
anstelle eines dictionary (__dict__) Nachschlagen. Ebenso ist jeder Code, der einen Wert zuweist, temperature
ruft automatisch an set_temperature()
.
Wir können sogar oben sehen, dass set_temperature()
wurde schon beim Anlegen eines Objekts aufgerufen.
>>> human = Celsius(37) Setting value...
Können Sie erraten, warum?
Der Grund dafür ist, dass beim Anlegen eines Objekts, die __init__()
wird Methode aufgerufen. Diese Methode hat die Zeile self.temperature = temperature
. Dieser Ausdruck ruft automatisch set_temperature()
.
Ebenso jeder Zugriff wie c.temperature
ruft automatisch get_temperature()
. Das ist es, was Eigentum tut. Hier sind ein paar weitere Beispiele.
>>> human.temperature Getting value 37 >>> human.temperature = 37 Setting value >>> c.to_fahrenheit() Getting value 98.60000000000001
Durch die Nutzung property
, sehen wir, dass keine Änderung in der Implementierung der Wertebeschränkung erforderlich ist. Unsere Implementierung ist also abwärtskompatibel.
Notiz
Der Temperatur-Istwert wird im privaten _temperature
variable. Das temperature
Attribut ist ein Eigenschaftsobjekt, das eine Schnittstelle zu dieser privaten Variablen bietet.
The @property Decorator
In Python ist property() eine built-in Funktion, die ein Property-Objekt erzeugt und zurückgibt. Die Syntax dieser Funktion lautet:
property(fget=None, fset=None, fdel=None, doc=None)
wo,
fget
ist eine Funktion, die den Wert des Attributsfset
ist eine Funktion zum Setzen des Wertes des Attributsfdel
ist eine Funktion zum Löschen des Attributsdoc
ist eine string (wie ein Kommentar)
Wie aus der Implementierung ersichtlich, sind diese Funktionsargumente optional. Ein Property-Objekt kann also einfach wie folgt erstellt werden.
>>> property() <property object at 0x0000000003239B38>
Ein Eigenschaftsobjekt hat drei Methoden, getter()
, setter()
, und deleter()
zu spezifizieren fget
, fset
und fdel
zu einem späteren Zeitpunkt. Das heißt, die Zeile:
temperature = property(get_temperature,set_temperature)
kann aufgeschlüsselt werden als:
# make empty property temperature = property() # assign fget temperature = temperature.getter(get_temperature) # assign fset temperature = temperature.setter(set_temperature)
Diese beiden Codestücke sind äquivalent.
Programmierer, die mit Python Decorators vertraut sind, können erkennen, dass das obige Konstrukt als Dekoratoren implementiert werden kann.
Wir können sogar die Namen nicht definieren get_temperature
and set_temperature
as they are unnecessary and pollute the class namespace.
Hierfür verwenden wir wieder die temperature
namen, während wir unsere Getter- und Setter-Funktionen definieren. Schauen wir uns an, wie man dies als Dekorator implementiert:
# Using @property decorator class Celsius: def __init__(self, temperature=0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32 @property def temperature(self): print("Getting value...") return self._temperature @temperature.setter def temperature(self, value): print("Setting value...") if value < -273.15: raise ValueError("Temperature below -273 is not possible") self._temperature = value # create an object human = Celsius(37) print(human.temperature) print(human.to_fahrenheit()) coldest_thing = Celsius(-300)
Output
Setting value... Getting value... 37 Getting value... 98.60000000000001 Setting value... Traceback (most recent call last): File "<string>", line 29, in <module> File "<string>", line 4, in __init__ File "<string>", line 18, in temperature ValueError: Temperature below -273 is not possible
Die obige Implementierung ist einfach und effizient. Es ist der empfohlene Weg zur Verwendung von property
.