W tym samouczku dowiesz się o dekoratorze @property w Pythonie; Pythoniczny sposób używania metod pobierających i ustawiających w programowaniu obiektowym.
Programowanie w Pythonie zapewnia nam wbudowany @property
dekorator, który znacznie ułatwia korzystanie z metod pobierających i ustawiających w programowaniu obiektowym.
Zanim przejdziemy do szczegółów na temat tego, czym @property
jest dekorator, najpierw zbudujmy intuicję, dlaczego byłby potrzebny w pierwszej kolejności.
Klasa bez metod pobierających i ustawiających
Załóżmy, że decydujemy się na stworzenie klasy przechowującej temperaturę w stopniach Celsjusza. Wdrożyłby również metodę przeliczania temperatury na stopnie Fahrenheita. Można to zrobić w następujący sposób:
class Celsius: def __init__(self, temperature = 0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32
Możemy tworzyć obiekty z tej klasy i manipulować temperature
atrybutem według własnego uznania:
# 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())
Wynik
37 98.60000000000001
Dodatkowe miejsca po przecinku przy konwersji na stopnie Fahrenheita wynikają z błędu arytmetycznego zmiennoprzecinkowego. Aby dowiedzieć się więcej, odwiedź stronę Python Floating Point Arithmetic Error.
Za każdym razem, gdy przypisujemy lub pobieramy dowolny atrybut obiektu, jak temperature
pokazano powyżej, Python wyszukuje go we wbudowanym __dict__
atrybucie słownikowym obiektu.
>>> human.__dict__ ('temperature': 37)
Dlatego man.temperature
staje się wewnętrznie man.__dict__('temperature')
.
Korzystanie z metod pobierających i ustawiających
Załóżmy, że chcemy rozszerzyć użyteczność zdefiniowanej powyżej klasy Celsjusza. Wiemy, że temperatura żadnego obiektu nie może spaść poniżej -273,15 stopni Celsjusza (zero absolutne w termodynamice)
Zaktualizujmy nasz kod, aby zaimplementował to ograniczenie wartości.
Oczywistym rozwiązaniem powyższego ograniczenia będzie ukrycie atrybutu temperature
(uczynienie go prywatnym) i zdefiniowanie nowych metod pobierających i ustawiających do manipulowania nim. Można to zrobić w następujący sposób:
# 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 widzimy, powyższy sposób wprowadza dwa nowe get_temperature()
i set_temperature()
metod.
Ponadto temperature
został zastąpiony _temperature
. Podkreślenie _
na początku służy do oznaczenia zmiennych prywatnych w Pythonie.
Teraz użyjmy tej implementacji:
# 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())
Wynik
37 98.60000000000001 Traceback (ostatnie połączenie ostatnio): Plik „”, wiersz 30, w pliku „”, wiersz 16, w set_temperature Wartość Błąd: Temperatura poniżej -273,15 nie jest możliwa.
Ta aktualizacja pomyślnie zaimplementowała nowe ograniczenie. Nie możemy już ustawić temperatury poniżej -273,15 stopni Celsjusza.
Uwaga : zmienne prywatne w rzeczywistości nie istnieją w Pythonie. Są po prostu normy, których należy przestrzegać. Sam język nie nakłada żadnych ograniczeń.
>>> human._temperature = -300 >>> human.get_temperature() -300
Jednak większym problemem związanym z powyższą aktualizacją jest to, że wszystkie programy, które implementowały naszą poprzednią klasę, muszą zmodyfikować swój kod od obj.temperature
do obj.get_temperature()
i wszystkie wyrażenia takie jak obj.temperature = val
to obj.set_temperature(val)
.
Ta refaktoryzacja może powodować problemy podczas obsługi setek tysięcy linii kodów.
Podsumowując, nasza nowa aktualizacja nie była kompatybilna wstecz. Tu @property
przychodzi na ratunek.
Właściwość Class
Pythonowym sposobem rozwiązania powyższego problemu jest użycie property
klasy. Oto jak możemy zaktualizować nasz 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śmy print()
funkcję wewnątrz get_temperature()
i set_temperature()
wyraźnie widać, że są wykonywane.
Ostatni wiersz kodu tworzy obiekt właściwości temperature
. Mówiąc najprościej, właściwość dołącza kod ( get_temperature
i set_temperature
) do atrybutu elementu członkowskiego accesses ( temperature
).
Użyjmy tego kodu aktualizacji:
# 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
Wynik
Wartość nastawy… Pobieranie wartości… 37 Pobieranie wartości… 98.60000000000001 Wartość ustawiana… Śledzenie (ostatnie połączenie): Plik „”, wiersz 31, w pliku „”, wiersz 18, w ustawionej_temperaturze Wartość Błąd: Temperatura poniżej -273 nie jest możliwa
Jak widać, każdy kod, który pobiera wartość temperature
, automatycznie wywoła get_temperature()
zamiast wyszukiwania słownikowego (__dict__). Podobnie każdy kod, który przypisuje wartość, temperature
będzie automatycznie wywoływał set_temperature()
.
Możemy nawet zobaczyć powyżej, że set_temperature()
zostało wywołane nawet wtedy, gdy stworzyliśmy obiekt.
>>> human = Celsius(37) Setting value…
Czy wiesz, dlaczego?
Powodem jest to, że kiedy tworzony jest obiekt, __init__()
wywoływana jest metoda. Ta metoda ma linię self.temperature = temperature
. To wyrażenie automatycznie wywołuje set_temperature()
.
Podobnie każdy dostęp, taki jak c.temperature
automatyczne połączenia get_temperature()
. To właśnie robi własność. Oto kilka innych przykładów.
>>> human.temperature Getting value 37 >>> human.temperature = 37 Setting value >>> c.to_fahrenheit() Getting value 98.60000000000001
Używając property
, widzimy, że nie jest wymagana żadna modyfikacja w realizacji ograniczenia wartości. W ten sposób nasza implementacja jest wstecznie kompatybilna.
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)
Te dwa fragmenty kodów są równoważne.
Programiści zaznajomieni z dekoratorami języka Python mogą rozpoznać, że powyższa konstrukcja może zostać zaimplementowana jako dekoratory.
Nie możemy nawet zdefiniować nazw, get_temperature
a set_temperature
ponieważ są one niepotrzebne i zanieczyszczają przestrzeń nazw klas.
W tym celu ponownie używamy temperature
nazwy podczas definiowania naszych funkcji pobierających i ustawiających. Spójrzmy, jak zaimplementować to jako 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)
Wynik
Ustawienie wartości… Pobieranie wartości… 37 Pobieranie wartości… 98.60000000000001 Ustawienie wartości… Śledzenie (ostatnie wywołanie ostatnie): Plik "", wiersz 29, w pliku "", wiersz 4, w __init__ Plik "", wiersz 18, w temperaturze Wartość Błąd: Temperatura poniżej -273 nie jest możliwa
Powyższa implementacja jest prosta i wydajna. Jest to zalecany sposób użycia property
.