@property

Zamienia metodę w atrybut: wartość jest wyliczana przy odczycie, a opcjonalny setter kontroluje zapis.

Na tej stronie

Przykład

#
Python
class Student:
    def __init__(self, name, points=0):
        self.name = name
        self.points = points

    @property
    def level(self):
        return self.points // 100 + 1

ania = Student("Ania", 250)
print(ania.level)

ania.points += 100
print(ania.level)

try:
    ania.level = 10
except AttributeError as error:
    print(error)
Wynikzapisany wynik, możesz go sprawdzić
3
4
property 'level' of 'Student' object has no setter

Definicja i zastosowanie

#

Dekorator @property sprawia, że metodę odczytujesz jak zwykły atrybut, bez nawiasów: student.level zamiast student.level(). Wartość jest wyliczana przy każdym odczycie, więc zawsze zgadza się z aktualnym stanem obiektu, np. poziom liczony z punktów.

Właściwość bez settera jest tylko do odczytu, a próba przypisania zgłasza AttributeError. Żeby pozwolić na zapis z kontrolą, dopisz drugą metodę o tej samej nazwie z dekoratorem @nazwa.setter. Setter może sprawdzić albo przekształcić nową wartość, zanim zapisze ją w atrybucie z podkreślnikiem, np. self._points.

Właściwości pozwalają zacząć od zwykłego atrybutu i dodać walidację później, bez zmieniania kodu, który z klasy korzysta. Dlatego w Pythonie nie pisze się na zapas metod get_points() i set_points(), jak w Javie. Wynik kosztownych obliczeń, który się nie zmienia, zapamięta dekorator functools.cached_property.

Składnia

#
Składnia
class Nazwa:
    @property
    def atrybut(self):
        return self._atrybut

    @atrybut.setter
    def atrybut(self, wartosc):
        self._atrybut = wartosc

Elementy właściwości

#
  • @property

    obiekt.x

    Getter, czyli metoda uruchamiana przy odczycie. Jej wynik staje się wartością atrybutu.
  • @x.setter

    obiekt.x = v

    Setter, czyli metoda uruchamiana przy przypisaniu. Dostaje nową wartość jako parametr. Bez niej atrybut jest tylko do odczytu.
  • @x.deleter

    del obiekt.x

    Metoda uruchamiana przy usuwaniu atrybutu. Potrzebna rzadko.

Więcej przykładów

#
Setter z walidacją
Python
class Student:
    def __init__(self, name, points=0):
        self.name = name
        self.points = points

    @property
    def points(self):
        return self._points

    @points.setter
    def points(self, value):
        if value < 0:
            raise ValueError("Punkty nie mogą być ujemne")
        self._points = value

kuba = Student("Kuba", 40)
kuba.points += 25
print(kuba.points)

try:
    kuba.points = -5
except ValueError as error:
    print(error)

print(kuba.points)
Wynikzapisany wynik, możesz go sprawdzić
65
Punkty nie mogą być ujemne
65

Przypisanie self.points = points w __init__ też przechodzi przez setter, więc obiektu z ujemną liczbą punktów nie da się nawet utworzyć.

Wynik liczony raz: cached_property
Python
from functools import cached_property

class Course:
    def __init__(self, title, lessons):
        self.title = title
        self.lessons = lessons

    @cached_property
    def total_minutes(self):
        print("Liczę czas trwania...")
        return sum(self.lessons)

python = Course("Python", [15, 20, 25])
print(python.total_minutes)
print(python.total_minutes)
Wynikzapisany wynik, możesz go sprawdzić
Liczę czas trwania...
60
60

Dobre praktyki

#
  • Właściwość powinna działać szybko i niczego nie zmieniać. Czytelnik kodu zakłada, że odczyt atrybutu to prosta, bezpieczna operacja.
  • Wartość przechowuj w atrybucie o innej nazwie, zwykle z podkreślnikiem (self._points). Zapis self.points = value wewnątrz settera wywoływałby ten sam setter w nieskończoność.
  • Nie twórz właściwości na zapas. Zacznij od zwykłego atrybutu i zamień go na @property, gdy pojawi się potrzeba walidacji albo wyliczania wartości.

Powiązane hasła

#

Widzisz błąd albo brakuje przykładu? Napisz do nas.