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?

TR.
źródło
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.

nosklo
źródło
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

Andrew Hows
źródło
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ć.

MrTopf
źródło
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
akotian
źródło
Nie tylko z __init__metody, ale z każdej innej metody w klasie.
Mia