Przykład z prawdziwego świata, jak korzystać z właściwości w Pythonie?

142

Interesuje mnie, jak używać @propertyw Pythonie. Przeczytałem dokumentację Pythona, a przykład tam, moim zdaniem, to tylko kod zabawki:

class C(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """I'm the 'x' property."""
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

Nie wiem, jakie korzyści mogę uzyskać dzięki owinięciu _xwypełnienia dekoratorem nieruchomości. Dlaczego nie wdrożyć jako:

class C(object):
    def __init__(self):
        self.x = None

Myślę, że funkcja właściwości może być przydatna w niektórych sytuacjach. Ale kiedy? Czy ktoś mógłby mi podać przykłady z prawdziwego świata?

Dzięki.

xiao 啸
źródło
9
To najlepsze i najczystsze wyjaśnienie, jakie znalazłem na temat dekoratora nieruchomości [kliknij tutaj ]
Sumudu
2
@Anubis w ostatnim przykładzie w podanym przez Ciebie łączu ustawienie c = Celsjusza (-500) nie zgłosiło żadnego błędu ValueError, co moim zdaniem nie daje zamierzonego wyniku.
Sajuuk
Zgadzam się z @Anubis. Jest poprawnie zaimplementowany tutaj: python-course.eu/python3_properties.php
anon01

Odpowiedzi:

91

Inne przykłady to walidacja / filtrowanie ustawionych atrybutów (zmuszanie ich do pozostawania w granicach lub akceptowalnych) oraz leniwa ocena złożonych lub szybko zmieniających się terminów.

Złożone obliczenia ukryte za atrybutem:

class PDB_Calculator(object):
    ...
    @property
    def protein_folding_angle(self):
        # number crunching, remote server calls, etc
        # all results in an angle set in 'some_angle'
        # It could also reference a cache, remote or otherwise,
        # that holds the latest value for this angle
        return some_angle

>>> f = PDB_Calculator()
>>> angle = f.protein_folding_angle
>>> angle
44.33276

Uprawomocnienie:

class Pedometer(object)
    ...
    @property
    def stride_length(self):
        return self._stride_length

    @stride_length.setter
    def stride_length(self, value):
        if value > 10:
            raise ValueError("This pedometer is based on the human stride - a stride length above 10m is not supported")
        else:
            self._stride_length = value
benosteen
źródło
1
Podoba mi się przykład PDB_Calculator - skomplikowane rzeczy są wyabstrahowane, całość działa, a użytkownik może cieszyć się prostotą!
Adam Kurkiewicz
2
być może, z pro-punktu widzenia, są to bardzo dobre przykłady. Ale jako noobie uważam, że te przykłady są dość nieskuteczne. mój zły ... :(
kmonsoor
77

Jednym prostym przypadkiem użycia będzie ustawienie atrybutu instancji tylko do odczytu, ponieważ wiesz, że prowadzenie nazwy zmiennej jednym podkreśleniem _xw pythonie zwykle oznacza, że ​​jest ona prywatna (do użytku wewnętrznego), ale czasami chcemy mieć możliwość odczytu atrybutu instancji, a nie pisania więc możemy użyć propertydo tego:

>>> class C(object):

        def __init__(self, x):
            self._x = x

        @property
        def x(self):
            return self._x

>>> c = C(1)
>>> c.x
1
>>> c.x = 2
AttributeError        Traceback (most recent call last)

AttributeError: can't set attribute
mouad
źródło
6
Nadal można ustawić c._x, jeśli użytkownik chce. Python w rzeczywistości nie ma prawdziwych atrybutów prywatnych.
20

Spójrz na ten artykuł, aby uzyskać bardzo praktyczne zastosowanie. Krótko mówiąc, wyjaśnia, w jaki sposób w Pythonie można zwykle porzucić jawną metodę getter / setter, ponieważ jeśli będziesz ich potrzebować na pewnym etapie, możesz użyć ich propertydo bezproblemowej implementacji.

Eli Bendersky
źródło
15

Jedną z rzeczy, do których go użyłem, jest buforowanie wolno przeszukiwanych, ale niezmiennych wartości przechowywanych w bazie danych. Uogólnia się to na każdą sytuację, w której atrybuty wymagają obliczeń lub innej długiej operacji (np. Sprawdzenie bazy danych, komunikacja sieciowa), którą chcesz wykonywać tylko na żądanie.

class Model(object):

  def get_a(self):
    if not hasattr(self, "_a"):
      self._a = self.db.lookup("a")
    return self._a

  a = property(get_a)

Było to w aplikacji internetowej, w której każdy widok strony mógł potrzebować tylko jednego określonego atrybutu tego rodzaju, ale same obiekty bazowe mogą mieć kilka takich atrybutów - inicjowanie ich wszystkich podczas konstrukcji byłoby marnotrawstwem, a właściwości pozwalają mi być elastycznym, w którym atrybuty są leniwe, a które nie.

bezczelnie
źródło
1
Nie możesz tego użyć @cached_property?
adarsh
@adarsh ​​- Brzmi interesująco. Gdzie to jest?
detly
Używałem
adarsh
2
Ciekawy. Myślę, że został opublikowany po raz pierwszy po tej odpowiedzi, ale każdy, kto to czyta, powinien prawdopodobnie z niej skorzystać.
detly
10

Czytając odpowiedzi i komentarze, wydaje się, że głównym tematem odpowiedzi wydaje się być brak prostego, ale użytecznego przykładu. Dołączyłem tutaj bardzo prosty przykład, który demonstruje proste użycie @propertydekoratora. Jest to klasa, która pozwala użytkownikowi określić i uzyskać pomiar odległości przy użyciu różnych jednostek, np . in_feetLub in_metres.

class Distance(object):
    def __init__(self):
        # This private attribute will store the distance in metres
        # All units provided using setters will be converted before
        # being stored
        self._distance = 0.0

    @property
    def in_metres(self):
        return self._distance

    @in_metres.setter
    def in_metres(self, val):
        try:
            self._distance = float(val)
        except:
            raise ValueError("The input you have provided is not recognised "
                             "as a valid number")

    @property
    def in_feet(self):
        return self._distance * 3.2808399

    @in_feet.setter
    def in_feet(self, val):
        try:
            self._distance = float(val) / 3.2808399
        except:
            raise ValueError("The input you have provided is not recognised "
                             "as a valid number")

    @property
    def in_parsecs(self):
        return self._distance * 3.24078e-17

    @in_parsecs.setter
    def in_parsecs(self, val):
        try:
            self._distance = float(val) / 3.24078e-17
        except:
            raise ValueError("The input you have provided is not recognised "
                             "as a valid number")

Stosowanie:

>>> distance = Distance()
>>> distance.in_metres = 1000.0
>>> distance.in_metres
1000.0
>>> distance.in_feet
3280.8399
>>> distance.in_parsecs
3.24078e-14
Mikrofon
źródło
dla mnie osobiście najlepszym przykładem metod pobierających / ustawiających jest pokazanie ludziom, jakie zmiany należy wprowadzić później, ale oczywiście zajmuje to trochę więcej czasu.
dtc
7

Właściwość to tylko abstrakcja wokół pola, która zapewnia większą kontrolę nad sposobami manipulowania określonym polem i wykonywania obliczeń oprogramowania pośredniego. Niewiele z przychodzących na myśl zastosowań to sprawdzanie poprawności i wcześniejsza inicjalizacja oraz ograniczenie dostępu

@property
def x(self):
    """I'm the 'x' property."""
    if self._x is None:
        self._x = Foo()

    return self._x
Stanislav Ageev
źródło
6

Tak, w opublikowanym oryginalnym przykładzie właściwość będzie działać dokładnie tak samo, jak po prostu posiadanie zmiennej instancji „x”.

To najlepsza rzecz we właściwościach Pythona. Z zewnątrz działają dokładnie jak zmienne instancji! Co pozwala na użycie zmiennych instancji spoza klasy.

Oznacza to, że Twój pierwszy przykład może faktycznie używać zmiennej instancji. Jeśli coś się zmieniło, a następnie zdecydujesz się zmienić implementację, a właściwość jest przydatna, interfejs tej właściwości będzie nadal taki sam z kodu spoza klasy. Zmiana ze zmiennej instancji na właściwość nie ma wpływu na kod poza klasą.

Wiele innych języków i kursów programowania instruuje programistę, aby nigdy nie ujawniał zmiennych instancji i zamiast tego używał „pobierających” i „ustawiających” dla dowolnej wartości, do której można uzyskać dostęp spoza klasy, nawet w przypadku prostego przypadku, jak podano w pytaniu.

Kod poza klasą z wykorzystaniem wielu języków (np. Java)

object.get_i()
    #and
object.set_i(value)

#in place of (with python)
object.i
    #and 
object.i = value

Podczas implementacji klasy istnieje wiele funkcji pobierających i ustawiających, które działają dokładnie tak, jak w pierwszym przykładzie: replikują zmienną o prostej instancji. Te metody pobierające i ustawiające są wymagane, ponieważ jeśli implementacja klasy ulegnie zmianie, cały kod poza klasą będzie musiał ulec zmianie. Ale właściwości Pythona pozwalają, aby kod poza klasą był taki sam, jak w przypadku zmiennych instancji. Dlatego kod poza klasą nie musi być zmieniany, jeśli dodajesz właściwość lub masz prostą zmienną instancji. Tak więc w przeciwieństwie do większości języków Object Oriented, dla prostego przykładu ty może użyć instancji zmiennej zamiast „pochłaniacze” i „ustawiaczy”, które nie są naprawdę potrzebne, bezpieczne, wiedząc, że jeśli zmieni się na nieruchomości w przyszłości, kod za pomocą twoja klasa nie musi się zmieniać.

Oznacza to, że potrzebujesz tworzyć właściwości tylko wtedy, gdy występuje złożone zachowanie, a w bardzo powszechnym prostym przypadku, w którym, jak opisano w pytaniu, wystarczy prosta zmienna instancji, możesz po prostu użyć zmiennej instancji.

innov8
źródło
6

kolejna fajna cecha właściwości w stosunku do używania setterów i getters to, że pozwalają ci kontynuować używanie operatorów OP = (np. + =, - =, * = itd.) na twoich atrybutach, jednocześnie zachowując walidację, kontrolę dostępu, buforowanie itp. setery i gettery będą dostarczać.

na przykład, jeśli napisałeś klasę Personz seterem setage(newage)i getter getage(), to aby zwiększyć wiek, musiałbyś napisać:

bob = Person('Robert', 25)
bob.setage(bob.getage() + 1)

ale jeśli stworzyłeś agenieruchomość, możesz napisać znacznie czystsze:

bob.age += 1
quizdog
źródło
5

Krótka odpowiedź na twoje pytanie jest taka, że ​​w twoim przykładzie nie ma żadnej korzyści. Powinieneś prawdopodobnie użyć formularza, który nie zawiera właściwości.

Powodem, dla którego istnieją właściwości jest to, że jeśli twój kod zmieni się w przyszłości i nagle będziesz musiał zrobić więcej ze swoimi danymi: wartości pamięci podręcznej, ochrona dostępu, przeszukiwanie zasobów zewnętrznych ... cokolwiek, możesz łatwo zmodyfikować swoją klasę, aby dodać metody pobierające i ustawiających dane bez zmiany interfejsu, więc nie musisz szukać wszędzie w kodzie, gdzie te dane są dostępne, i też tego zmieniać.

SpoonMeiser
źródło
4

Coś, czego wielu na początku nie zauważa, to fakt, że możesz tworzyć własne podklasy własności. To okazało się bardzo przydatne do ujawniania atrybutów obiektu tylko do odczytu lub atrybutu, który można odczytać i zapisać, ale nie można go usunąć. Jest to również doskonały sposób na zawijanie funkcji, takich jak śledzenie modyfikacji pól obiektów.

class reader(property):
    def __init__(self, varname):
        _reader = lambda obj: getattr(obj, varname)
        super(reader, self).__init__(_reader)

class accessor(property):
    def __init__(self, varname, set_validation=None):
        _reader = lambda obj: getattr(obj, varname)
        def _writer(obj, value):
            if set_validation is not None:
               if set_validation(value):
                  setattr(obj, varname, value)
        super(accessor, self).__init__(_reader, _writer)

#example
class MyClass(object):
   def __init__(self):
     self._attr = None

   attr = reader('_attr')

źródło
Lubię to. Czy czytam to poprawnie w tym czytniku jest tylko do odczytu, podczas gdy akcesor jest do odczytu / zapisu bez możliwości usuwania? Jak jednak dodasz weryfikację danych? Jestem całkiem nowy w Pythonie, ale myślę, że prawdopodobnie istnieje sposób na dodanie wywołania zwrotnego do attr = reader('_attr')linii lub jakiejś formy wstępnego sprawdzania, na przykład attr = if self.__isValid(value): reader('_attr'). Propozycje?
Gabe Spradlin
Przepraszam, właśnie zdałem sobie sprawę, że pytam o walidację danych dla zmiennej tylko do odczytu. Ale oczywiście dotyczy to tylko części ustawiającej klasy akcesorium. Więc zmień attr = reader('_attr')na attr = accessor('_attr'). Dzięki
Gabe Spradlin
Masz rację, że gdybyś chciał walidacji, dodałbyś funkcję do walidacji i wygenerowania wyjątku, jeśli jest nieprawidłowy (lub jakiekolwiek inne zachowanie, które lubisz, w tym nie robienie niczego) do init . Zmodyfikowałem powyższe jednym możliwym wzorem. Walidator powinien zwrócić True | False, aby wskazać, czy zestaw się wydarzy, czy nie.