2016-11-03 3 views
2

Я получаю ошибку Can't set attribute, когда я использую оператор += для свойства только для чтения, которое имеет тип, для которого я определил метод __iadd__().Как использовать оператор + = для свойства, которое не имеет сеттера?

упрощенные (но работоспособные) версия моего кода:

class Emitter(list): 
    def __iadd__(self, other): 
     self.append(other) 
     return self 

class Widget: 
    def __init__(self): 
     self._on_mouseenter = Emitter() 

    @property 
    def on_mouseenter(self): return self._on_mouseenter 


my_widget = Widget() 
my_widget.on_mouseenter += lambda source: print("on_mouseenter!") 

Последняя строка выдает ошибку. Она уходит, если добавить следующую строку в определении Widget:

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

(Runnable в https://repl.it/EONf/0)

Такое поведение кажется странным на два счета. Во-первых, я думал, что Python передает объекты по ссылке, так почему же свойство должно быть читаемым? И, во-вторых, как получилось, что мой фиктивный сеттер работает?

+0

Ответы объясняют проблему, однако вы можете просто определить 'self.on_mouseenter = Emitter()' непосредственно и избежать свойства. –

+0

Будьте осторожны с вашей терминологией. В то время как Python передает ссылки на объекты, «pass by reference» относится к чему-то тонко другому. У Python действительно нет хорошего стандартного имени; некоторые люди называют это «вызовом по объекту», «вызовом путем совместного использования» или, как говорят люди Java, «передать по значению, но переданное значение является ссылкой». – user2357112

+0

@BenHoyt: Я хотел сделать свойство доступным только для чтения, чтобы код пользователя не мог его перезаписать. Это антипаттерн в Python? – JPNotADragon

ответ

3

__iadd__ возвращает объект замены, подлежащий отскоку до переменной. Для этого, конечно, нужен сеттер.

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

Это поведение необходимо, так как некоторые объекты неизменяемы, но на месте добавляются работы по ним.

i += 5 берет число i, которое добавляет к нему 5 и переводит i в новый номер результата. То есть он точно эквивалентен i = i + 5, который имеет в нем задание.

+0

Спасибо @Max. Скажете ли вы, что мое решение правильное? – JPNotADragon

+0

Не совсем: он должен назначить переданное ему значение, или вы можете получить очень неожиданные результаты, если '__iadd__' не возвращает себя. – Max

+0

Использование + = для обозначения append не является особенно pythonic. Люди будут использоваться для работы со списками с помощью .append(), а не + =. – Max

3

Это связано с тем, как работает Python augmented assignment operators. После вызова соответствующего special method они присваивают возвращаемое значение объекту в левой части оператора.

Если x является экземпляром класса с __iadd__() методом, x += y эквивалентно x = x.__iadd__(y). В противном случае x.__add__(y) и y.__radd__(x) считаются , так как при оценке x + y.

Поэтому

my_widget.on_mouseenter += lambda source: print("on_mouseenter!") 

эквивалентно

my_widget.on_mouseenter = my_widget.on_mouseenter.__iadd__(lambda source: print("on_mouseenter!")) 

и вам нужно сеттера для выполнения задания. Это не нужно ничего делать, потому что метод __iadd__ определен и изменяет список на месте.

+2

Для дальнейшего чтения см. [Этот ответ] (https://docs.python.org/3.5/faq/programming.html#faq-augmented-assignment-tuple-error) в FAQ Python. –

+0

Спасибо, это было действительно информативно. – JPNotADragon

+0

@vaultah: Я принял ответ «Макс» только потому, что он был вторым или тем быстрее. Спасибо вам обоим. – JPNotADragon

Смежные вопросы