¿Por qué @ foo.setter en Python no funciona para mí?

155

Entonces, estoy jugando con decoradores en Python 2.6, y tengo algunos problemas para que funcionen. Aquí está mi archivo de clase:

class testDec:

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

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

Lo que pensé que significaba era tratar xcomo una propiedad, pero llamar a estas funciones en get y set. Entonces, encendí IDLE y lo comprobé:

>>> 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, la primera llamada funciona como se esperaba, ya que llamo al getter, y no hay un valor predeterminado, y falla. OK, bien, lo entiendo. Sin embargo, la llamada a asignar t.x = 5parece crear una nueva propiedad x, ¡y ahora el captador no funciona!

¿Qué me estoy perdiendo?

TR.
fuente
66
Por favor nombre las clases con una letra mayúscula. puede usar minúsculas para variables y archivos de módulos.
S.Lott

Respuestas:

306

Parece que está utilizando clases clásicas de estilo antiguo en python 2. Para que las propiedades funcionen correctamente, debe utilizar clases de estilo nuevo (en python 2 debe heredar deobject ). Simplemente declara tu clase 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
>>> 

Otro detalle que puede causar problemas es que ambos métodos necesitan el mismo nombre para que la propiedad funcione. Si define el setter con un nombre diferente como este, no funcionará :

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

Y una cosa más que no es completamente fácil de detectar al principio, es el orden: el captador debe definirse primero . Si define primero el setter, obtendrá un name 'x' is not definederror.

nosklo
fuente
44
En Python3 ya no es necesario: las clases "clásicas" están bien con los setters :-)
Eenoku
20
Funciona en Python 3 porque cada clase es una clase de estilo nuevo, ya no hay clases 'clásicas'.
craigds
Creo que uno debería recibir una advertencia / error, si el decorador no se procesa correctamente en este caso.
stfn
82

Solo una nota para otras personas que tropiezan aquí buscando esta excepción: ambas funciones deben tener el mismo nombre. Nombrar los métodos de la siguiente manera dará como resultado una excepción:

@property
def x(self): pass

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

En su lugar, dale a ambos métodos el mismo nombre

@property
def x(self): pass

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

También es importante tener en cuenta que el orden de la declaración es importante. El captador debe definirse antes que el definidor en el archivo o de lo contrario obtendrá unNameError: name 'x' is not defined

Andrew Hows
fuente
Esta probablemente se convertirá en la "nueva" mejor respuesta, con más y más personas en Python 3 ...
trpt4him
55
Esta respuesta es genial. Creo que para estar completo, sería genial si puedes comentar que el orden es importante, es decir, el captador debe estar antes que el establecedor en el código. De lo contrario, obtendría una NameError: name 'x' is not definedexcepción.
eguaio
Gracias por esto. Acabo de perder la mayor parte del día en esto. Nunca se me ocurrió que los métodos debían llamarse de la misma manera. ¡Un modismo muy frustrante!
ajkavanagh
Para comprender el "por qué" detrás de esta respuesta, lea la documentación definitiva aquí: docs.python.org/2/library/functions.html#property Observe especialmente la parte "exactamente equivalente".
Evgeni Sergeev
23

Debes usar clases de estilo nuevo que haces derivando tu clase del objeto:

class testDec(object):
   ....

Entonces debería funcionar.

MrTopf
fuente
Todo lo anterior estaba en su lugar. Esto fue necesario para mí en python 2.7.x para que las propiedades de las clases se patearan correctamente.
Drone Brain
12

En caso de que alguien venga de google, además de las respuestas anteriores, me gustaría agregar que esto requiere una atención cuidadosa al invocar al setter desde el __init__método de su clase basado en esta respuesta Específicamente:

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
fuente
No solo del __init__método, sino de cualquier otro método de la clase.
Mia