Почему @ foo.setter в Python не работает для меня?

155

Итак, я играю с декораторами в Python 2.6, и у меня возникают некоторые проблемы с их работой. Вот мой файл класса:

class testDec:

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

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

Я думал, что это означает, что нужно обращаться xсо свойством, но вызывать эти функции при получении и установке. Итак, я запустил IDLE и проверил это:

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

Ясно, что первый вызов работает, как и ожидалось, так как я вызываю метод получения, и значения по умолчанию не существует, и он завершается ошибкой. ОК, хорошо, я понимаю. Тем не менее, вызов метода assign t.x = 5создает новое свойство x, и теперь метод get не работает!

Чего мне не хватает?

TR.
источник
6
Пожалуйста, назовите классы заглавными буквами. Вы можете использовать строчные буквы для переменных и файлов модулей.
S.Lott

Ответы:

306

Похоже, вы используете классические классы старого стиля в Python 2. Чтобы свойства работали правильно, вам нужно использовать классы нового стиля (в Python 2 вы должны наследовать отobject ). Просто объявите свой класс как 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

Оно работает:

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

Другая деталь, которая может вызвать проблемы, заключается в том, что оба метода должны иметь одинаковое имя для работы свойства. Если вы определите сеттер с другим именем, как это, он не будет работать :

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

И еще одна вещь, которую поначалу не совсем легко обнаружить, это порядок: сначала нужно определить получатель . Если вы сначала определите установщик, вы получите name 'x' is not definedошибку.

nosklo
источник
4
В Python3 больше не нужно - «классические» классы просто в порядке с сеттерами :-)
Eenoku
20
Это работает в Python 3, потому что каждый класс является классом нового стиля, больше нет «классических» классов.
Крейгдс
Я думаю, что следует получить предупреждение / ошибку, если декоратор не обрабатывается правильно в этом случае.
stfn
82

Просто примечание для других людей, которые спотыкаются здесь, ища это исключение: обе функции должны иметь одинаковое имя. Наименование методов следующим образом приведет к исключению:

@property
def x(self): pass

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

Вместо этого дайте обоим методам одно и то же имя

@property
def x(self): pass

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

Также важно отметить, что порядок декларации имеет значение. Получатель должен быть определен до установки в файле, иначе вы получитеNameError: name 'x' is not defined

Эндрю Хоус
источник
Это, вероятно, станет "новым" лучшим ответом, с все большим количеством людей на Python 3 ...
trpt4him
5
Этот ответ великолепен. Я думаю, что для полноты, было бы замечательно, если бы вы могли заметить, что порядок важен, то есть получатель должен быть перед установщиком в коде. В противном случае вы получите NameError: name 'x' is not definedисключение.
eguaio
Спасибо за это. Я просто потратил лучшую часть дня на это. Мне никогда не приходило в голову, что методы должны называться одинаково. Действительно расстраивает маленькая идиома!
ajkavanagh
Чтобы понять «почему» за этим ответом, прочитайте полную документацию здесь: docs.python.org/2/library/functions.html#property Особо обратите внимание на «точно эквивалентную» часть.
Евгений Сергеев
24

Вам нужно использовать классы нового стиля, которые вы делаете, получая свой класс из объекта:

class testDec(object):
   ....

Тогда это должно работать.

MrTopf
источник
Все выше было на месте. Это было необходимо для меня в Python 2.7.x для свойств в классах, чтобы правильно пнуть.
Drone Brain
12

В случае, если кто-то пришел сюда из Google, в дополнение к ответам выше, я хотел бы добавить, что это требует тщательного внимания при вызове __init__метода установки из метода вашего класса на основе этого ответа. В частности:

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
источник
Не только из __init__метода, но и из любого другого метода в классе.
Миа