Dlaczego @ foo.setter w Pythonie nie działa dla mnie?


155

Więc bawię się dekoratorami w Pythonie 2.6 i mam problemy z ich uruchomieniem. Oto plik mojej klasy:

class testDec:

    @property
    def x(self): 
        print 'called getter'
        return self._x

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

Pomyślałem, że oznaczało to traktowanie xjak właściwość, ale wywołanie tych funkcji podczas pobierania i ustawiania. Tak więc odpaliłem IDLE i sprawdziłem:

>>> from testDec import testDec
from testDec import testDec
>>> t = testDec()
t = testDec()
>>> t.x
t.x
called getter
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "testDec.py", line 18, in x
    return self._x
AttributeError: testDec instance has no attribute '_x'
>>> t.x = 5
t.x = 5
>>> t.x
t.x
5

Oczywiście pierwsze wywołanie działa zgodnie z oczekiwaniami, ponieważ wywołuję getter i nie ma wartości domyślnej i kończy się niepowodzeniem. OK, dobrze, rozumiem. Jednak wywołanie przypisania t.x = 5wydaje się tworzyć nową właściwość x, a teraz funkcja pobierająca nie działa!

czego mi brakuje?


6
Nazwij klasy wielką literą. możesz używać małych liter dla zmiennych i plików modułów.
— S.Lott

Odpowiedzi:


306

Wydaje się, że używasz klasycznych klas w starym stylu w Pythonie 2. Aby właściwości działały poprawnie, musisz zamiast tego użyć klas nowego stylu (w Pythonie 2 musisz dziedziczyć zobject ). Po prostu zadeklaruj swoją klasę jako MyClass(object):

class testDec(object):

    @property
    def x(self): 
        print 'called getter'
        return self._x

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

To działa:

>>> k = testDec()
>>> k.x
called getter
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/devel/class_test.py", line 6, in x
    return self._x
AttributeError: 'testDec' object has no attribute '_x'
>>> k.x = 5
called setter
>>> k.x
called getter
5
>>> 

Innym szczegółem, który może powodować problemy, jest to, że obie metody wymagają tej samej nazwy, aby właściwość działała. Jeśli zdefiniujesz setera z inną nazwą, jak ta, to nie zadziała :

@x.setter
def x_setter(self, value):
    ...

I jeszcze jedna rzecz, która na początku nie jest łatwa do zauważenia, to kolejność: najpierw należy zdefiniować metodę pobierającą . Jeśli najpierw zdefiniujesz ustawiającego, pojawi się name 'x' is not definedbłąd.


4
W Pythonie3 nie jest to już konieczne - klasy „klasyczne” są po prostu w porządku z seterami :-)
— Eenoku

20
Działa w Pythonie 3, ponieważ każda klasa jest klasą w nowym stylu, nie ma już klas „klasycznych”.
— craigds

Myślę, że powinno się dostać ostrzeżenie / błąd, jeśli dekorator nie jest w tym przypadku poprawnie przetwarzany.
— stfn

82

Tylko uwaga dla innych osób, które natkną się tutaj, szukając tego wyjątku: obie funkcje muszą mieć tę samą nazwę. Nazwanie metod w następujący sposób spowoduje wyjątek:

@property
def x(self): pass

@x.setter
def x_setter(self, value): pass

Zamiast tego nadaj obu metodom tę samą nazwę

@property
def x(self): pass

@x.setter
def x(self, value): pass

Należy również zauważyć, że kolejność deklaracji ma znaczenie. Getter musi być zdefiniowany przed ustawiaczem w pliku, w przeciwnym razie otrzymasz rozszerzenieNameError: name 'x' is not defined


Prawdopodobnie stanie się to „nową” najlepszą odpowiedzią, z coraz większą liczbą osób korzystających z Pythona 3 ...
— trpt4him

5
Ta odpowiedź jest świetna. Myślę, że aby być kompletnym, byłoby wspaniale, gdybyś mógł zauważyć, że kolejność jest ważna, to znaczy, że pobierający musi znajdować się przed ustawiaczem w kodzie. W przeciwnym razie wystąpiłby NameError: name 'x' is not definedwyjątek.
— eguaio

Dzięki za to. Po prostu zmarnowałem na to najlepszą część dnia. Nigdy nie przyszło mi do głowy, że metody trzeba nazywać tym samym. Naprawdę frustrujący mały idiom!
— ajkavanagh

Aby zrozumieć „dlaczego” kryjące się za tą odpowiedzią, przeczytaj pełną dokumentację tutaj: docs.python.org/2/library/functions.html#property Zwróć szczególną uwagę na część „dokładnie równoważną”.
— Evgeni Sergeev

23

Musisz użyć klas nowego stylu, które robisz, wyprowadzając swoją klasę z obiektu:

class testDec(object):
   ....

Wtedy powinno działać.


Wszystko powyżej było na swoim miejscu. Było to wymagane w Pythonie 2.7.x, aby właściwości w klasach działały prawidłowo.
— Drone Brain

12

W przypadku, gdy ktoś przychodzi tutaj z Google, oprócz powyższych odpowiedzi chciałbym dodać, że wymaga to szczególnej uwagi przy wywoływaniu setera z __init__metody Twojej klasy opartej na tej odpowiedzi. Konkretnie:

class testDec(object):                                                                                                                                            

    def __init__(self, value):
        print 'We are in __init__'
        self.x = value # Will call the setter. Note just x here
        #self._x = value # Will not call the setter

    @property
    def x(self):
        print 'called getter'
        return self._x # Note the _x here

    @x.setter
    def x(self, value): 
        print 'called setter'
        self._x = value # Note the _x here

t = testDec(17)
print t.x 

Output:
We are in __init__
called setter
called getter
17

Nie tylko z __init__metody, ale z każdej innej metody w klasie.
— Mia
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.