Python @property: jak go używać i dlaczego? - Programiz

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 @propertydekorator, 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 @propertyjest 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ć temperatureatrybutem 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 temperaturepokazano powyżej, Python wyszukuje go we wbudowanym __dict__atrybucie słownikowym obiektu.

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

Dlatego man.temperaturestaje 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 temperaturezostał 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.temperaturedo obj.get_temperature()i wszystkie wyrażenia takie jak obj.temperature = valto 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 @propertyprzychodzi na ratunek.

Właściwość Class

Pythonowym sposobem rozwiązania powyższego problemu jest użycie propertyklasy. 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_temperaturei 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ść, temperaturebę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.temperatureautomatyczne 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 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)

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_temperaturea set_temperatureponieważ są one niepotrzebne i zanieczyszczają przestrzeń nazw klas.

W tym celu ponownie używamy temperaturenazwy 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.

Interesujące artykuły...