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 Attributs
  • fset ist eine Funktion zum Setzen des Wertes des Attributs
  • fdel ist eine Funktion zum Löschen des Attributs
  • doc 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.