Python @property: Cum se folosește și de ce? - Programiz

În acest tutorial, veți afla despre Python @property decorator; un mod pitonic de a utiliza getters și setere în programare orientată obiect.

Programarea Python ne oferă un @propertydecorator încorporat, care face mult mai ușoară utilizarea getterului și a setatoarelor în programarea orientată pe obiecte.

Înainte de a intra în detalii despre ce @propertyeste decoratorul, haideți mai întâi să construim o intuiție cu privire la motivul pentru care ar fi nevoie în primul rând.

Clasa Fără Getters și Setters

Să presupunem că decidem să facem o clasă care stochează temperatura în grade Celsius. De asemenea, ar implementa o metodă de conversie a temperaturii în grade Fahrenheit. Un mod de a face acest lucru este următorul:

 class Celsius: def __init__(self, temperature = 0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32

Putem crea obiecte din această clasă și temperatureputem manipula atributul după cum dorim:

 # Basic method of setting and getting attributes in Python class Celsius: def __init__(self, temperature=0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32 # Create a new object human = Celsius() # Set the temperature human.temperature = 37 # Get the temperature attribute print(human.temperature) # Get the to_fahrenheit method print(human.to_fahrenheit())

Ieșire

 37 98.60000000000001

Zecimale suplimentare la conversia în Fahrenheit se datorează erorii aritmetice în virgulă mobilă. Pentru a afla mai multe, accesați Python Floating Point Arithmetic Error.

Ori de câte ori atribuim sau preluăm orice atribut de obiect, așa temperaturecum se arată mai sus, Python îl caută în __dict__atributul de dicționar încorporat al obiectului .

 >>> human.__dict__ ('temperature': 37)

Prin urmare, man.temperatureintern devine man.__dict__('temperature').

Folosind Getters și Seters

Să presupunem că dorim să extindem utilizabilitatea clasei Celsius definite mai sus. Știm că temperatura oricărui obiect nu poate ajunge sub -273,15 grade Celsius (zero absolut în termodinamică)

Să ne actualizăm codul pentru a implementa această constrângere de valoare.

O soluție evidentă la restricția de mai sus va fi să ascundem atributul temperature(să îl facem privat) și să definim noi metode getter și setter pentru a-l manipula. Acest lucru se poate face după cum urmează:

 # 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

După cum putem vedea, metoda de mai sus introduce două metode noi get_temperature()și set_temperature().

Mai mult, a temperaturefost înlocuit cu _temperature. O subliniere _la început este utilizată pentru a desemna variabile private în Python.

Acum, să folosim această implementare:

 # 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())

Ieșire

 37 98.60000000000001 Traceback (ultimul apel cel mai recent): Fișier "", linia 30, în Fișier "", linia 16, în set_temperature ValueError: Temperatura sub -273,15 nu este posibilă.

Această actualizare a implementat cu succes noua restricție. Nu ne mai este permis să setăm temperatura sub -273,15 grade Celsius.

Notă : Variabilele private nu există de fapt în Python. Există pur și simplu norme care trebuie respectate. Limba în sine nu aplică restricții.

 >>> human._temperature = -300 >>> human.get_temperature() -300

Cu toate acestea, problema cea mai mare cu actualizarea de mai sus este că toate programele care au implementat clasa noastră anterioară trebuie să-și modifice codul de obj.temperaturela obj.get_temperature()și toate expresiile ca obj.temperature = valla obj.set_temperature(val).

Această refactorizare poate provoca probleme în timp ce se ocupă de sute de mii de linii de coduri.

Una peste alta, noua noastră actualizare nu a fost compatibilă înapoi. Aici @propertyvine salvarea.

Clasa proprietății

O modalitate pitonică de a rezolva problema de mai sus este utilizarea propertyclasei. Iată cum ne putem actualiza codul:

 # 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)

Am adăugat o print()funcție în interior get_temperature()și set_temperature()pentru a observa clar că acestea sunt executate.

Ultima linie a codului face obiectul unei proprietăți temperature. Pur și simplu, proprietatea atașează un anumit cod ( get_temperatureși set_temperature) accesului la atributul membru ( temperature).

Să folosim acest cod de actualizare:

 # 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

Ieșire

 Setarea valorii … Obținerea valorii … 37 Obținerea valorii … 98.60000000000001 Valoarea setării … Traceback (ultimul apel cel mai recent): Fișier "", linia 31, în Fișier "", linia 18, în set_temperature ValueError: Temperatura sub -273 nu este posibil

As we can see, any code that retrieves the value of temperature will automatically call get_temperature() instead of a dictionary (__dict__) look-up. Similarly, any code that assigns a value to temperature will automatically call set_temperature().

We can even see above that set_temperature() was called even when we created an object.

 >>> human = Celsius(37) Setting value… 

Can you guess why?

The reason is that when an object is created, the __init__() method gets called. This method has the line self.temperature = temperature. This expression automatically calls set_temperature().

Similarly, any access like c.temperature automatically calls get_temperature(). This is what property does. Here are a few more examples.

 >>> human.temperature Getting value 37 >>> human.temperature = 37 Setting value >>> c.to_fahrenheit() Getting value 98.60000000000001

By using property, we can see that no modification is required in the implementation of the value constraint. Thus, our implementation is backward compatible.

Note: The actual temperature value is stored in the private _temperature variable. The temperature attribute is a property object which provides an interface to this private variable.

The @property Decorator

In Python, property() is a built-in function that creates and returns a property object. The syntax of this function is:

 property(fget=None, fset=None, fdel=None, doc=None)

where,

  • fget is function to get value of the attribute
  • fset is function to set value of the attribute
  • fdel is function to delete the attribute
  • doc is a string (like a comment)

As seen from the implementation, these function arguments are optional. So, a property object can simply be created as follows.

 >>> property() 

A property object has three methods, getter(), setter(), and deleter() to specify fget, fset and fdel at a later point. This means, the line:

 temperature = property(get_temperature,set_temperature)

can be broken down as:

 # make empty property temperature = property() # assign fget temperature = temperature.getter(get_temperature) # assign fset temperature = temperature.setter(set_temperature)

Aceste două bucăți de coduri sunt echivalente.

Programatorii familiarizați cu Python Decorators pot recunoaște că construcția de mai sus poate fi implementată ca decoratori.

Nu putem chiar defini numele get_temperatureși, set_temperaturedeoarece acestea sunt inutile și poluează spațiul de nume al clasei.

Pentru aceasta, refolosim temperaturenumele în timp ce ne definim funcțiile getter și setter. Să vedem cum să punem în aplicare acest lucru ca decorator:

 # 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)

Ieșire

 Setarea valorii … Obținerea valorii … 37 Obținerea valorii … 98.60000000000001 Valoarea setării … Traceback (ultimul apel cel mai recent): Fișier "", linia 29, în Fișier "", linia 4, în __init__ Fișier "", linia 18, în temperatură ValueError: Temperatura sub -273 nu este posibilă

Implementarea de mai sus este simplă și eficientă. Este modul recomandat de utilizare property.

Articole interesante...