Jaki jest pythonowy sposób używania pobierających i ustawiających?

340

Robię to tak:

def set_property(property,value):  
def get_property(property):  

lub

object.property = value  
value = object.property

Jestem nowy w Pythonie, więc wciąż badam składnię i chciałbym uzyskać porady na ten temat.

Jorge Guberte
źródło

Odpowiedzi:

684

Spróbuj tego: Właściwość Python

Przykładowy kod to:

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

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

    @x.setter
    def x(self, value):
        print("setter of x called")
        self._x = value

    @x.deleter
    def x(self):
        print("deleter of x called")
        del self._x


c = C()
c.x = 'foo'  # setter called
foo = c.x    # getter called
del c.x      # deleter called
Grissiom
źródło
2
Czy seter dla x jest wywoływany w inicjalizatorze podczas tworzenia instancji _x?
Casey
7
@Casey: Nie. Odwołania do ._x(które nie są własnością, tylko zwykłym atrybutem) omijają propertyzawijanie. Tylko odniesienia do .xprzejścia przez property.
ShadowRanger
277

Jaki jest pythonowy sposób używania pobierających i ustawiających?

„Pythońskim” sposobem nie jest używanie „pobierających” i „ustawiających”, ale używanie prostych atrybutów, jak pokazuje pytanie, i deldo usuwania (ale nazwy są zmieniane, aby chronić niewinne ... wbudowane):

value = 'something'

obj.attribute = value  
value = obj.attribute
del obj.attribute

Jeśli później chcesz zmodyfikować ustawienie i uzyskać, możesz to zrobić bez konieczności zmiany kodu użytkownika, używając propertydekoratora:

class Obj:
    """property demo"""
    #
    @property            # first decorate the getter method
    def attribute(self): # This getter method name is *the* name
        return self._attribute
    #
    @attribute.setter    # the property decorates with `.setter` now
    def attribute(self, value):   # name, e.g. "attribute", is the same
        self._attribute = value   # the "value" name isn't special
    #
    @attribute.deleter     # decorate with `.deleter`
    def attribute(self):   # again, the method name is the same
        del self._attribute

(Każde użycie dekoratora kopiuje i aktualizuje poprzedni obiekt właściwości, dlatego należy pamiętać, że należy używać tej samej nazwy dla każdego zestawu, funkcji pobierania i usuwania funkcji / metody.

Po zdefiniowaniu powyższego oryginalne ustawienie, pobieranie i usuwanie kodu jest takie samo:

obj = Obj()
obj.attribute = value  
the_value = obj.attribute
del obj.attribute

Powinieneś tego unikać:

def set_property(property,value):  
def get_property(property):  

Po pierwsze powyższe nie działa, ponieważ nie podajesz argumentu dla instancji, dla której właściwość byłaby ustawiona na (zwykle self), która byłaby:

class Obj:

    def set_property(self, property, value): # don't do this
        ...
    def get_property(self, property):        # don't do this either
        ...

Po drugie, powiela to cel dwóch specjalnych metod __setattr__oraz __getattr__.

Po trzecie, mamy też setattri getattrwbudowanych funkcji.

setattr(object, 'property_name', value)
getattr(object, 'property_name', default_value)  # default is optional

@propertyDekorator jest do tworzenia pobierające i ustawiające.

Na przykład, moglibyśmy zmodyfikować zachowanie ustawienia, aby nałożyć ograniczenia na ustawianą wartość:

class Protective(object):

    @property
    def protected_value(self):
        return self._protected_value

    @protected_value.setter
    def protected_value(self, value):
        if acceptable(value): # e.g. type or range check
            self._protected_value = value

Ogólnie rzecz biorąc, chcemy unikać używania propertyi po prostu używać atrybutów bezpośrednich.

Tego oczekują użytkownicy Pythona. Zgodnie z zasadą najmniejszego zaskoczenia powinieneś spróbować dać użytkownikom to, czego oczekują, chyba że masz bardzo ważny powód, aby postąpić inaczej.

Demonstracja

Powiedzmy na przykład, że potrzebowaliśmy, aby atrybut chroniony naszego obiektu był liczbą całkowitą od 0 do 100 włącznie i zapobiegał jego usunięciu, z odpowiednimi komunikatami informującymi użytkownika o jego właściwym użyciu:

class Protective(object):
    """protected property demo"""
    #
    def __init__(self, start_protected_value=0):
        self.protected_value = start_protected_value
    # 
    @property
    def protected_value(self):
        return self._protected_value
    #
    @protected_value.setter
    def protected_value(self, value):
        if value != int(value):
            raise TypeError("protected_value must be an integer")
        if 0 <= value <= 100:
            self._protected_value = int(value)
        else:
            raise ValueError("protected_value must be " +
                             "between 0 and 100 inclusive")
    #
    @protected_value.deleter
    def protected_value(self):
        raise AttributeError("do not delete, protected_value can be set to 0")

(Należy zauważyć, że __init__odnosi się do, self.protected_valueale odnoszą się do niego metody własności self._protected_value. Ma to na celu __init__wykorzystanie właściwości za pośrednictwem publicznego interfejsu API, zapewniając jej „ochronę”).

I użycie:

>>> p1 = Protective(3)
>>> p1.protected_value
3
>>> p1 = Protective(5.0)
>>> p1.protected_value
5
>>> p2 = Protective(-5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __init__
  File "<stdin>", line 15, in protected_value
ValueError: protectected_value must be between 0 and 100 inclusive
>>> p1.protected_value = 7.3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 17, in protected_value
TypeError: protected_value must be an integer
>>> p1.protected_value = 101
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 15, in protected_value
ValueError: protectected_value must be between 0 and 100 inclusive
>>> del p1.protected_value
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 18, in protected_value
AttributeError: do not delete, protected_value can be set to 0

Czy imiona mają znaczenie?

Tak, robią . .setteri .deleterwykonaj kopie oryginalnej nieruchomości. Pozwala to podklasom odpowiednio modyfikować zachowanie bez zmiany zachowania w obiekcie nadrzędnym.

class Obj:
    """property demo"""
    #
    @property
    def get_only(self):
        return self._attribute
    #
    @get_only.setter
    def get_or_set(self, value):
        self._attribute = value
    #
    @get_or_set.deleter
    def get_set_or_delete(self):
        del self._attribute

Teraz, aby to zadziałało, musisz użyć odpowiednich nazw:

obj = Obj()
# obj.get_only = 'value' # would error
obj.get_or_set = 'value'  
obj.get_set_or_delete = 'new value'
the_value = obj.get_only
del obj.get_set_or_delete
# del obj.get_or_set # would error

Nie jestem pewien, gdzie byłoby to przydatne, ale przypadek użycia dotyczy sytuacji, gdy potrzebujesz właściwości get, set i / lub delete-only. Prawdopodobnie najlepiej trzymać się semantycznie tej samej własności o tej samej nazwie.

Wniosek

Zacznij od prostych atrybutów.

Jeśli później potrzebujesz funkcji związanej z ustawieniem, uzyskiwaniem i usuwaniem, możesz dodać ją za pomocą dekoratora właściwości.

Unikaj nazwanych funkcji set_...i get_...- po to są właściwości.

Aaron Hall
źródło
Oprócz powielania dostępnych funkcji, dlaczego należy unikać pisania własnych setterów i getterów? Rozumiem, że może to nie być sposób Pythona, ale czy są naprawdę poważne problemy, które można napotkać inaczej?
user1350191
4
W twoim demo __init__metoda odnosi się, self.protected_valueale do gettera i settera self._protected_value. Czy możesz wyjaśnić, jak to działa? Przetestowałem twój kod i działa tak, jak jest - więc to nie jest literówka.
codeforester
2
@codeforester Miałem nadzieję odpowiedzieć wcześniej w mojej odpowiedzi, ale dopóki nie będę mógł, ten komentarz powinien wystarczyć. Mam nadzieję, że zobaczysz, że korzysta z tej właściwości za pośrednictwem publicznego interfejsu API, zapewniając jej „ochronę”. Czy nie ma sensu „chronić” go właściwością, a następnie używać niepublicznego interfejsu API zamiast __init__tego?
Aaron Hall
2
Tak, @AaronHall już to ma. Nie zdawałem sobie sprawy, że self.protected_value = start_protected_valuetak naprawdę wywołuje funkcję setera; Myślałem, że to zadanie.
codeforester
1
imho to powinna być zaakceptowana odpowiedź, jeśli dobrze zrozumiałem, python zajmuje dokładnie przeciwny punkt w porównaniu do np. java. Zamiast domyślnie
ustawiać
27
In [1]: class test(object):
    def __init__(self):
        self.pants = 'pants'
    @property
    def p(self):
        return self.pants
    @p.setter
    def p(self, value):
        self.pants = value * 2
   ....: 
In [2]: t = test()
In [3]: t.p
Out[3]: 'pants'
In [4]: t.p = 10
In [5]: t.p
Out[5]: 20
Autoplektyka
źródło
17

Używanie @propertyi @attribute.setterpomaga nie tylko używać „pythonowego” sposobu, ale także sprawdzać poprawność atrybutów zarówno podczas tworzenia obiektu, jak i podczas jego zmiany.

class Person(object):
    def __init__(self, p_name=None):
        self.name = p_name

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, new_name):
        if type(new_name) == str: #type checking for name property
            self._name = new_name
        else:
            raise Exception("Invalid value for name")

Dzięki temu faktycznie „ukrywasz” _nameatrybut przed programistami klientów, a także sprawdzasz typ właściwości nazwy. Zauważ, że postępując zgodnie z tym podejściem, nawet podczas inicjacji, setter zostaje wywołany. Więc:

p = Person(12)

Zaprowadzi do:

Exception: Invalid value for name

Ale:

>>>p = person('Mike')
>>>print(p.name)
Mike
>>>p.name = 'George'
>>>print(p.name)
George
>>>p.name = 2.3 # Causes an exception
Farzad Vertigo
źródło
16

Sprawdź @propertydekorator .

Kevin Little
źródło
34
To właściwie odpowiedź tylko do linku.
Aaron Hall
7
Jak to jest pełna odpowiedź? Link nie jest odpowiedzią.
Andy_A̷n̷d̷y̷
Myślę, że to dobra odpowiedź, ponieważ dokumentacja wyraźnie określa, jak z niej korzystać (i pozostanie aktualna, jeśli zmieni się implementacja Pythona, i przekieruje OP do metody, którą sugeruje odpowiedź. @ Jean-FrançoisCorbett wyraźnie stwierdził „jak” to pełna odpowiedź
HunnyBear
W każdym razie ta odpowiedź nie dodaje niczego przydatnego do innych odpowiedzi i najlepiej powinna zostać usunięta.
Georgy,
5

Możesz używać akcesoriów / mutatorów (tj. @attr.setterI @property) lub nie, ale najważniejsze jest zachowanie spójności!

Jeśli używasz @propertypo prostu uzyskać dostęp do atrybutu, np

class myClass:
    def __init__(a):
        self._a = a

    @property
    def a(self):
        return self._a

użyj go, aby uzyskać dostęp do każdego * atrybutu! Złą praktyką byłoby uzyskiwanie dostępu do niektórych atrybutów za pomocą @propertyi pozostawienie niektórych innych właściwości publicznych (np. Nazwa bez podkreślenia) bez akcesorium, np. Nie rób

class myClass:
    def __init__(a, b):
        self.a = a
        self.b = b

    @property
    def a(self):
        return self.a

Pamiętaj, że self.bnie ma tutaj wyraźnego akcesorium, nawet jeśli jest publiczne.

Podobnie z seterami (lub mutatorami ), nie krępuj się używać, @attribute.setterale bądź konsekwentny! Kiedy robisz np

class myClass:
    def __init__(a, b):
        self.a = a
        self.b = b 

    @a.setter
    def a(self, value):
        return self.a = value

Trudno mi odgadnąć twoją intencję. Z jednej strony mówisz, że oba ai bsą publiczne (brak wiodących znaków podkreślenia w ich nazwach), więc teoretycznie powinienem mieć dostęp do / mutowania (get / set) obu. Ale wtedy podajesz tylko jawny mutator dla a, który mówi mi, że może nie powinienem być w stanie ustawić b. Ponieważ podałeś wyraźny mutator, nie jestem pewien, czy brak wyraźnego accessor ( @property) oznacza, że ​​nie powinienem mieć dostępu do żadnej z tych zmiennych, czy po prostu byłeś oszczędny w użyciu @property.

* Wyjątkiem jest sytuacja, gdy jawnie chcesz, aby niektóre zmienne były dostępne lub zmienne, ale nie jedno lub drugie, lub chcesz wykonać dodatkową logikę podczas uzyskiwania dostępu lub modyfikowania atrybutu. To jest, kiedy osobiście używam @propertyi @attribute.setter(w przeciwnym razie nie ma wyraźnych acessorów / mutatorów dla publicznych atrybutów).

Wreszcie sugestie PEP8 i Przewodnika po stylu Google:

PEP8, Designing for Inheritance mówi:

W przypadku prostych publicznych atrybutów danych najlepiej jest ujawnić tylko nazwę atrybutu, bez skomplikowanych metod dostępu / mutatora . Należy pamiętać, że Python zapewnia łatwą ścieżkę do przyszłego rozszerzenia, jeśli okaże się, że prosty atrybut danych musi zwiększyć funkcjonalne zachowanie. W takim przypadku użyj właściwości, aby ukryć funkcjonalną implementację kryjącą się za prostą składnią dostępu do atrybutów danych.

Z drugiej strony, zgodnie z Google Style Guide Python Language Rules / Properties zaleca się:

Użyj właściwości w nowym kodzie, aby uzyskać dostęp do danych lub ustawić je tam, gdzie normalnie użyłbyś prostych, lekkich metod akcesorów lub ustawiaczy. Właściwości należy utworzyć za pomocą @propertydekoratora.

Zalety tego podejścia:

Czytelność jest zwiększona poprzez wyeliminowanie jawnych wywołań metod get i set dla łatwego dostępu do atrybutów. Pozwala lenić obliczenia. Uważany za Pythoniczny sposób utrzymania interfejsu klasy. Jeśli chodzi o wydajność, zezwalanie właściwościom na omijanie wymagających prostych metod dostępu, gdy bezpośredni dostęp do zmiennych jest uzasadniony. Pozwala to również na dodanie metod akcesorów w przyszłości bez zrywania interfejsu.

i minusy:

Musi dziedziczyć objectpo Pythonie 2. Może ukrywać skutki uboczne, podobnie jak przeciążenie operatora. Może być mylące dla podklas.

Tomasz Bartkowiak
źródło
1
Definitywnie się z tym nie zgadzam. Jeśli mam 15 atrybutów na moim obiekcie i chcę, aby jeden został obliczony @property, to użycie reszty również @propertywydaje się złą decyzją.
Quelklef
Zgadzam się, ale tylko wtedy, gdy jest coś specyficznego w tym konkretnym atrybucie, którego potrzebujesz @property(np. Wykonanie specjalnej logiki przed zwróceniem atrybutu). W przeciwnym razie dlaczego miałbyś ozdobić jeden atrybut @properyinnym, a nie innym?
Tomasz Bartkowiak
@Quelklef zobacz sidenote w poście (oznaczony gwiazdką).
Tomasz Bartkowiak
Cóż ... Jeśli nie robisz żadnej rzeczy wymienionej przez sidenote, to nie powinieneś używać @propertyna początku, prawda? Jeśli twój getter jest, return this._xa twój seter jest this._x = new_x, to używanie @propertyw ogóle jest trochę głupie.
Quelklef
1
Hmmm, może. Osobiście powiedziałbym, że to nie w porządku --- jest całkowicie zbędne. Ale widzę, skąd pochodzisz. Wydaje mi się, że właśnie przeczytałem Twój post, mówiąc, że „najważniejszą rzeczą podczas korzystania @propertyjest konsekwencja”.
Quelklef
-1

Możesz użyć magicznych metod __getattribute__i __setattr__.

class MyClass:
    def __init__(self, attrvalue):
        self.myattr = attrvalue
    def __getattribute__(self, attr):
        if attr == "myattr":
            #Getter for myattr
    def __setattr__(self, attr):
        if attr == "myattr":
            #Setter for myattr

Pamiętaj o tym __getattr__i __getattribute__nie są takie same. __getattr__jest wywoływane tylko wtedy, gdy atrybut nie zostanie znaleziony.

Pika the Wizard of the Whales
źródło