Dekorator @property pozwala przekształcić metodę w "wirtualny" atrybut klasy: wygląda to dla użytkownika klasy jak zwykła właściwość, ale zarządzane jest logiką funkcji Pythona. Pierwotnie w Pythonie wszystkie atrybuty instancji były dostępne bezpośrednio, ale w celu wsparcia enkapsulacji potrzebny był mechanizm zarządzania dostępem do danych bez zmiany interfejsu klasy.
Historia:
W wczesnych wersjach Pythona nie było wyraźnego mechanizmu ograniczania dostępu do atrybutów. Problem enkapsulacji rozwiązywano poprzez konwencje (na przykład, podkreślenie), ale wszelkie zmiany w logice przechowywania/walidacji wartości naruszały kompatybilność. Dzięki wprowadzeniu dekoratora @property pojawiła się możliwość zadeklarowania metody jako atrybutu, z odpowiednimi getterem, setterem i deleterem, zachowując poprzedni interfejs klasy.
Problem:
Kiedy później trzeba dodać logikę walidacji lub obliczenia wartości pola, przebudowa całego interfejsu byłaby zbyt kosztowna — trzeba zmieniać wszystkie miejsca dostępu do zmiennej. Przy tym widoczność wewnętrznej implementacji pola nie powinna być ujawniona zewnętrznemu użytkownikowi klasy. @property pozwala łatwo "wirtualizować" dostęp.
Rozwiązanie:
@property realizuje wzorzec "getter/setter z ochroną interfejsu": można dodać obliczane właściwości lub kontrolować ustawianie wartości bez zmiany kodu klienta.
Przykład kodu:
class Temperature: def __init__(self, celsius): self._celsius = celsius @property def celsius(self): return self._celsius @celsius.setter def celsius(self, value): if value < -273.15: raise ValueError("Temperatura poniżej zera bezwzględnego!") self._celsius = value @property def fahrenheit(self): return self._celsius * 9 / 5 + 32 # Użycie obj = Temperature(25) print(obj.celsius) # 25 print(obj.fahrenheit) # 77.0 obj.celsius = -300 # ValueError
Kluczowe cechy:
Czy zawsze można dodać property do dowolnego pola klasy bez naruszania zgodności wstecznej?
Nie. Jeśli atrybut był publiczny i był używany jak zwykła zmienna, zamiana na property może być dokonana bez przejrzystości. Ale jeśli gdzieś odwołania odbywały się przez __dict__ lub wykonywano sprawdzania typu przez type(obj.attr), może to prowadzić do nieoczekiwanego zachowania.
Czy można zadeklarować tylko setter bez gettera dla właściwości?
Nie, na początku wymagany jest getter (metoda z @property), w przeciwnym razie Python nie wie, do jakiej właściwości "przypisać" setter.
Jaką kolejność mają dekoratory @property/@setter/@deleter i dlaczego?
Zawsze najpierw pisze się @property (tworzy obiekt właściwości), a następnie przez nazwę metody — @<name>.setter i @<name>.deleter (uzupełniają wcześniej stworzone property).
class A: @property def value(self): ... @value.setter def value(self, v): ...
Pola klasy były publiczne, a następnie dodano obliczaną właściwość przez property po wydaniu publicznego API. W starym kodzie odwołanie do atrybutu niejawnie zmienia zachowanie lub wywołuje błąd.
Zalety:
Wady:
Już na etapie projektowania pola były zdefiniowane jako prywatne (z jednym podkreśleniem), a użytkownicy pracowali tylko za pośrednictwem metod/właściwości. Dodanie nowej logiki do property w przyszłości przebiegło bez zmian w interfejsie klienta.
Zalety:
Wady: