V tomto výukovém programu se dozvíte o dekorátoru Python @property; pythonický způsob použití getrů a setterů v objektově orientovaném programování.
Programování v Pythonu nám poskytuje vestavěný @property
dekorátor, díky němuž je používání getru a setteru mnohem jednodušší v objektově orientovaném programování.
Než se @property
pustíme do podrobností o tom, co je to dekoratér, nejprve si vytvořme intuici, proč by to vůbec bylo potřeba.
Třída bez getterů a setterů
Předpokládejme, že se rozhodneme vytvořit třídu, která uchovává teplotu ve stupních Celsia. Rovněž by implementovalo metodu převodu teploty na stupně Fahrenheita. Jeden způsob, jak toho dosáhnout, je následující:
class Celsius: def __init__(self, temperature = 0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32
Z této třídy můžeme vytvářet objekty a manipulovat s temperature
atributem, jak si přejeme:
# 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())
Výstup
37 98.60000000000001
Extra desetinná místa při převodu na Fahrenheita jsou způsobena aritmetickou chybou s plovoucí desetinnou čárkou. Další informace najdete v aritmetické chybě s plovoucí desetinnou čárkou v Pythonu.
Kdykoli přiřadíme nebo načteme jakýkoli atribut objektu, temperature
jak je uvedeno výše, Python jej prohledá v __dict__
atributu vestavěného slovníku objektu.
>>> human.__dict__ ('temperature': 37)
Proto se man.temperature
vnitřně stává man.__dict__('temperature')
.
Používání getrů a setrů
Předpokládejme, že chceme rozšířit použitelnost třídy Celsia definované výše. Víme, že teplota žádného objektu nemůže dosáhnout pod -273,15 stupňů Celsia (absolutní nula v termodynamice)
Aktualizujme náš kód, abychom implementovali toto omezení hodnoty.
Zřejmým řešením výše uvedeného omezení bude skrýt atribut temperature
(nastavit jej jako soukromý) a definovat nové metody getter a setter pro manipulaci s ním. To lze provést následovně:
# 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
Jak vidíme, výše uvedená metoda zavádí dvě nové get_temperature()
a set_temperature()
metody.
Dále temperature
byl nahrazen _temperature
. Podtržítko _
na začátku se používá k označení soukromých proměnných v Pythonu.
Nyní použijeme tuto implementaci:
# 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())
Výstup
37 98.60000000000001 Traceback (poslední poslední hovor): Soubor "", řádek 30, v souboru "", řádek 16, v set_temperature ValueError: Teplota pod -273,15 není možná.
Tato aktualizace úspěšně implementovala nové omezení. Již nemůžeme nastavovat teplotu pod -273,15 stupňů Celsia.
Poznámka : Soukromé proměnné ve skutečnosti v Pythonu neexistují. Je prostě třeba dodržovat normy. Samotný jazyk neplatí žádná omezení.
>>> human._temperature = -300 >>> human.get_temperature() -300
Nicméně, větší problém s výše aktualizace je, že všechny programy, které zavedly náš předchozí třídu muset změnit svůj kód z obj.temperature
k obj.get_temperature()
a všechny výrazy jako obj.temperature = val
pro obj.set_temperature(val)
.
Toto refaktorování může způsobit problémy při řešení stovek tisíc řádků kódů.
Celkově vzato nebyla naše nová aktualizace zpětně kompatibilní. Tady @property
přichází záchrana.
Vlastnost Class
Pythonickým způsobem řešení výše uvedeného problému je použití property
třídy. Takto můžeme aktualizovat náš kód:
# 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)
Přidali jsme print()
funkci dovnitř get_temperature()
a set_temperature()
abychom jasně viděli, že se provádějí.
Poslední řádek kódu vytvoří objekt vlastnosti temperature
. Jednoduše řečeno, vlastnost připojí nějaký kód ( get_temperature
a set_temperature
) k atributu člena accesses ( temperature
).
Pojďme použít tento aktualizační kód:
# 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
Výstup
Nastavení hodnoty … Získání hodnoty … 37 Získání hodnoty … 98.60000000000001 Nastavení hodnoty … Traceback (poslední hovor poslední): Soubor "", řádek 31, v souboru "", řádek 18, v set_temperature ValueError: Teplota pod -273 není možná
Jak vidíme, jakýkoli kód, který načte hodnotu, temperature
bude automaticky volat get_temperature()
místo vyhledávání slovníku (__dict__). Podobně temperature
bude automaticky volat jakýkoli kód, který přiřadí hodnotu set_temperature()
.
Můžeme dokonce vidět výše, že to set_temperature()
bylo voláno, i když jsme vytvořili objekt.
>>> human = Celsius(37) Setting value…
Uhodnete proč?
Důvodem je to, že když je objekt vytvořen, __init__()
metoda se zavolá. Tato metoda má řádek self.temperature = temperature
. Tento výraz automaticky volá set_temperature()
.
Podobně jakýkoli přístup, jako je c.temperature
automatické volání get_temperature()
. To je to, co dělá vlastnost. Zde je několik dalších příkladů.
>>> human.temperature Getting value 37 >>> human.temperature = 37 Setting value >>> c.to_fahrenheit() Getting value 98.60000000000001
Použitím property
vidíme, že při implementaci omezení hodnoty není nutná žádná změna. Naše implementace je tedy zpětně kompatibilní.
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 attributefset
is function to set value of the attributefdel
is function to delete the attributedoc
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)
Tyto dva kódy jsou ekvivalentní.
Programátoři obeznámení s dekorátory Pythonu mohou rozpoznat, že výše uvedený konstrukt lze implementovat jako dekoratéry.
Dokonce nemůžeme definovat názvy get_temperature
a set_temperature
protože jsou zbytečné a znečišťují jmenný prostor třídy.
Z tohoto důvodu znovu používáme temperature
název a zároveň definujeme naše funkce getter a setter. Podívejme se, jak to implementovat jako dekoratér:
# 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)
Výstup
Nastavení hodnoty … Získání hodnoty … 37 Získání hodnoty … 98.60000000000001 Nastavení hodnoty … Traceback (poslední volání poslední): Soubor "", řádek 29, v souboru "", řádek 4, v __init__ Soubor "", řádek 18, v teplotě ValueError: Teplota pod -273 ° C není možná
Výše uvedená implementace je jednoduchá a efektivní. Je to doporučený způsob použití property
.