Por que @ foo.setter em Python não funciona para mim?

155

Então, estou brincando com os decoradores no Python 2.6 e estou tendo alguns problemas para fazê-los funcionar. Aqui está o meu arquivo de classe:

class testDec:

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

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

O que eu pensei que isso significava é tratar xcomo uma propriedade, mas chame essas funções de get e set. Então, iniciei o IDLE e verifiquei:

>>> 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

Claramente, a primeira chamada funciona como esperado, desde que eu chamo o getter, e não há valor padrão, e ele falha. OK, bom, eu entendo. No entanto, a chamada para atribuir t.x = 5parece criar uma nova propriedade xe agora o getter não funciona!

o que estou perdendo?

TR.
fonte
6
Nomeie Classes Com Uma Letra Maiúscula. você pode usar letras minúsculas para variáveis ​​e arquivos de módulo.
315/09 S.Lott

Respostas:

306

Parece que você está usando classes clássicas de estilo antigo no python 2. Para que as propriedades funcionem corretamente, você precisa usar classes de estilo novo (no python 2 você deve herdarobject ). Apenas declare sua classe como 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

Funciona:

>>> 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
>>> 

Outro detalhe que pode causar problemas é que os dois métodos precisam do mesmo nome para que a propriedade funcione. Se você definir o setter com um nome diferente como este, não funcionará :

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

E mais uma coisa que não é completamente fácil de identificar a princípio, é a ordem: o getter deve ser definido primeiro . Se você definir o setter primeiro, obtém name 'x' is not defined erro.

nosklo
fonte
4
Em Python3 é necessário não mais - as classes "clássicos" são apenas ok com setters :-)
Eenoku
20
Ele funciona no python 3 porque cada classe é uma classe de novo estilo, não há mais classes 'clássicas'.
craigds 23/06
Eu acho que é preciso receber um aviso / erro, se o decorador não for processado corretamente neste caso.
Stfn 19/07/19
82

Apenas uma observação para outras pessoas que tropeçam aqui procurando por essa exceção: ambas as funções precisam ter o mesmo nome. Nomear os métodos da seguinte maneira resultará em uma exceção:

@property
def x(self): pass

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

Em vez disso, dê a ambos os métodos o mesmo nome

@property
def x(self): pass

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

Também é importante observar que a ordem da declaração é importante. O getter deve ser definido antes do setter no arquivo, ou você receberá umaNameError: name 'x' is not defined

Andrew Hows
fonte
Este é provavelmente vai se tornar o "novo" melhor resposta, com mais e mais pessoas em Python 3 ...
trpt4him
5
Esta resposta é ótima. Eu acho que, para ser completo, seria ótimo se você pudesse observar que a ordem é importante, ou seja, o getter deve estar antes do setter no código. Caso contrário, você receberá uma NameError: name 'x' is not definedexceção.
eguaio
Obrigado por isso. Eu perdi a melhor parte de um dia nisso. Nunca me ocorreu que os métodos devessem ser chamados da mesma coisa. Pequeno idioma realmente frustrante!
ajkavanagh
Para entender o "porquê" por trás desta resposta, leia a documentação definitiva aqui: docs.python.org/2/library/functions.html#property Observe especialmente a parte "exatamente equivalente".
Evgeni Sergeev
23

Você precisa usar classes de novo estilo, derivando sua classe do objeto:

class testDec(object):
   ....

Então deve funcionar.

MrTopf
fonte
Tudo acima estava no lugar. Isso foi necessário para mim no python 2.7.x para que as propriedades nas classes fossem executadas corretamente.
Drone Brain
12

Caso alguém venha aqui do google, além das respostas acima, gostaria de acrescentar que isso precisa de muita atenção ao chamar o setter do __init__método da sua classe com base nesta resposta Especificamente:

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
fonte
Não apenas do __init__método, mas de qualquer outro método da classe.
Mia