Python @property: Kako ga koristiti i zašto? - Programiz

U ovom vodiču naučit ćete o uređivaču Python @property; pitonski način upotrebe getera i postavljača u objektno orijentiranom programiranju.

Programiranje na Pythonu pruža nam ugrađeni @propertyuređivač koji olakšava upotrebu gettera i postavljača u objektno orijentiranom programiranju.

Prije nego što @propertyulazimo u detalje o tome što je dekorator, prvo izgradimo intuiciju zašto bi uopće bio potreban.

Razred bez getera i postavljača

Pretpostavimo da smo se odlučili za klasu koja pohranjuje temperaturu u Celzijevim stupnjevima. Također bi primijenio metodu za pretvaranje temperature u stupnjeve Fahrenheita. Jedan od načina za to je sljedeći:

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

Iz ove klase možemo napraviti predmete i upravljati temperatureatributom kako želimo:

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

Izlaz

 37 98,60000000000001

Dodatne decimale pri pretvaranju u Fahrenheit posljedica su aritmetičke pogreške s pomičnim zarezom. Da biste saznali više, posjetite Python aritmetičku pogrešku s pomičnim zarezom.

Kad god dodijelimo ili dohvatimo bilo koji atribut objekta kao temperaturešto je prikazano gore, Python ga pretražuje u ugrađenom __dict__atributu rječnika objekta.

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

Stoga, man.temperatureiznutra postaje man.__dict__('temperature').

Korištenje Gettera i Settera

Pretpostavimo da želimo proširiti upotrebljivost gore definirane Celzijeve klase. Znamo da temperatura bilo kojeg objekta ne može doseći ispod -273,15 Celzijevih stupnjeva (Apsolutna Nula u termodinamici)

Ažurirajmo naš kôd kako bismo implementirali ovo ograničenje vrijednosti.

Očito rješenje za gornje ograničenje bit će sakriti atribut temperature(učiniti ga privatnim) i definirati nove metode dobivanja i postavljanja kako bi se njime manipuliralo. To se može učiniti na sljedeći način:

 # 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

Kao što vidimo, gornja metoda uvodi dvije nove get_temperature()i set_temperature()metode.

Nadalje, temperaturezamijenjen je s _temperature. Podvlaka _na početku koristi se za označavanje privatnih varijabli u Pythonu.

Sada, iskoristimo ovu implementaciju:

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

Izlaz

 37 98.60000000000001 Traceback (najnoviji zadnji poziv): Datoteka "", redak 30, u datoteci "", redak 16, u postavci_temperature ValueError: Temperatura ispod -273,15 nije moguća.

Ovo je ažuriranje uspješno primijenilo novo ograničenje. Više ne smijemo postavljati temperaturu ispod -273,15 Celzijevih stupnjeva.

Napomena : Privatne varijable zapravo ne postoje u Pythonu. Jednostavno postoje norme kojih se treba pridržavati. Sam jezik ne primjenjuje nikakva ograničenja.

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

Međutim, veći problem s gore ažuriranja da su svi programi koji se provode naš prethodni razred moraju mijenjati svoj kod iz obj.temperatureda obj.get_temperature()i sve izraze kao što obj.temperature = valbi obj.set_temperature(val).

Ova refaktorizacija može stvoriti probleme dok se bavite stotinama tisuća redaka kodova.

Sve u svemu, naše novo ažuriranje nije bilo unatrag kompatibilno. Tu @propertydolazi spašavanje.

Klasa svojstva

Pitonski način rješavanja gornjeg problema je upotreba propertyklase. Evo kako možemo ažurirati naš kod:

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

Dodali smo print()funkciju iznutra get_temperature()i set_temperature()jasno uočili da se izvršavaju.

Posljednji redak koda čini objekt svojstva temperature. Jednostavno rečeno, svojstvo pridružuje neki kod ( get_temperaturei set_temperature) pristupu atributa člana ( temperature).

Upotrijebimo ovaj kôd za ažuriranje:

 # 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

Izlaz

 Postavljanje vrijednosti … Dobivanje vrijednosti … 37 Dobivanje vrijednosti … 98.60000000000001 Postavljanje vrijednosti … Traceback (zadnji poziv zadnji): Datoteka "", red 31, u datoteci "", red 18, u postavci_temperature ValueError: Temperatura ispod -273 nije moguća

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)

Ova dva dijela kodova su ekvivalentna.

Programeri upoznati s Python Decorators mogu prepoznati da se gornji konstrukt može implementirati kao dekorator.

Čak ne možemo definirati imena, get_temperaturea set_temperaturekako su nepotrebna i zagađuju prostor imena klase.

U tu svrhu ponovno koristimo temperaturenaziv dok definiramo naše funkcije dobivanja i postavljanja. Pogledajmo kako to implementirati kao dekorator:

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

Izlaz

 Postavljanje vrijednosti … Dobivanje vrijednosti … 37 Dobivanje vrijednosti … 98.60000000000001 Postavljanje vrijednosti … Traceback (posljednji zadnji poziv): Datoteka "", red 29, u datoteci "", red 4, u __init__ Datoteka "", red 18, u temperaturi ValueError: Temperatura ispod -273 nije moguća

Gornja implementacija je jednostavna i učinkovita. To je preporučeni način upotrebe property.

Zanimljivi članci...