Pourquoi @ foo.setter en Python ne fonctionne-t-il pas pour moi?

155

Donc, je joue avec des décorateurs en Python 2.6, et j'ai du mal à les faire fonctionner. Voici mon fichier 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

Ce que je pensais que cela signifiait, c'était de traiter xcomme une propriété, mais appelez ces fonctions sur get and set. Alors, j'ai lancé IDLE et l'ai vérifié:

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

Clairement, le premier appel fonctionne comme prévu, puisque j'appelle le getter, et il n'y a pas de valeur par défaut, et cela échoue. OK, bien, je comprends. Cependant, l'appel à assigner t.x = 5semble créer une nouvelle propriété x, et maintenant le getter ne fonctionne plus!

Qu'est-ce que je rate?

TR.
la source
6
Veuillez nommer les classes avec une lettre majuscule. vous pouvez utiliser des minuscules pour les variables et les fichiers de module.
S.Lott

Réponses:

306

Vous semblez utiliser des classes classiques de l'ancien style en python 2. Pour que les propriétés fonctionnent correctement, vous devez utiliser des classes de style nouveau à la place (en python 2, vous devez hériter deobject ). Déclarez simplement votre classe comme 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

Ça marche:

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

Un autre détail qui peut causer des problèmes est que les deux méthodes ont besoin du même nom pour que la propriété fonctionne. Si vous définissez le setter avec un nom différent comme celui-ci, cela ne fonctionnera pas :

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

Et une autre chose qui n'est pas complètement facile à repérer au début, c'est l'ordre: le getter doit être défini en premier . Si vous définissez d'abord le setter, vous obtenez une name 'x' is not definederreur.

nosklo
la source
4
En Python3, ce n'est plus nécessaire - les classes "classiques" sont juste ok avec les setters :-)
Eenoku
20
Cela fonctionne en python 3 car chaque classe est une classe de style nouveau, il n'y a plus de classes «classiques».
craigds
Je pense que l'on devrait recevoir un avertissement / une erreur, si le décorateur n'est pas traité correctement dans ce cas.
stfn
82

Juste une note pour les autres personnes qui trébuchent ici à la recherche de cette exception: les deux fonctions doivent avoir le même nom. Nommer les méthodes comme suit entraînera une exception:

@property
def x(self): pass

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

Donnez plutôt le même nom aux deux méthodes

@property
def x(self): pass

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

Il est également important de noter que l'ordre de la déclaration compte. Le getter doit être défini avant le setter dans le fichier sinon vous obtiendrez unNameError: name 'x' is not defined

Andrew Hows
la source
Cela va probablement devenir la "nouvelle" meilleure réponse, avec de plus en plus de gens sur Python 3 ...
trpt4him
5
Cette réponse est excellente. Je pense que pour être complet, ce serait formidable si vous pouviez remarquer que l'ordre est important, c'est-à-dire que le getter doit être avant le setter dans le code. Sinon, vous obtiendrez une NameError: name 'x' is not definedexception.
eguaio
Merci pour cela. J'ai juste gaspillé la meilleure partie de la journée avec ça. Il ne m'est jamais venu à l'esprit que les méthodes devaient être appelées la même chose. Petit idiome vraiment frustrant!
ajkavanagh
Pour comprendre le "pourquoi" derrière cette réponse, lisez la documentation définitive ici: docs.python.org/2/library/functions.html#property Notez en particulier la partie "exactement équivalente".
Evgeni Sergeev
24

Vous devez utiliser des classes de style nouveau, ce que vous faites en dérivant votre classe de object:

class testDec(object):
   ....

Ensuite, cela devrait fonctionner.

MrTopf
la source
Tout ce qui précède était en place. Cela était nécessaire pour moi dans python 2.7.x pour que les propriétés des classes fonctionnent correctement.
Drone Brain
12

Dans le cas où quelqu'un vient ici de Google, en plus des réponses ci-dessus, je voudrais ajouter que cela nécessite une attention particulière lors de l'appel du setter à partir de la __init__méthode de votre classe basée sur cette réponse Plus précisément:

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
akotien
la source
Pas seulement de la __init__méthode, mais de toute autre méthode de la classe.
Mia