foo += bar кажется, влияет на каждый экземпляр класса, тогда как foo = foo + bar кажется, ведет себя так, как я ожидал бы, что все будет вести себя.
Оператор += называется "составным оператором присваивания".
Переведено автоматически
Ответ 1
Общий ответ заключается в том, что += пытается вызвать __iadd__ специальный метод, и если он недоступен, он пытается использовать __add__ вместо этого. Итак, проблема заключается в разнице между этими специальными методами.
__iadd__ Специальный метод предназначен для добавления на месте, то есть он изменяет объект, на который воздействует. __add__ Специальный метод возвращает новый объект и также используется для стандартного + оператора.
Итак, когда += оператор используется для объекта, у которого есть __iadd__ определенный объект, объект изменяется на месте. В противном случае вместо этого он попытается использовать обычный __add__ и вернет новый объект.
Вот почему для изменяемых типов, таких как списки, += изменяет значение объекта, тогда как для неизменяемых типов, таких как кортежи, строки и целые числа, вместо этого возвращается новый объект (a += b становится эквивалентным a = a + b).
Поэтому для типов, которые поддерживают оба __iadd__ и __add__, вы должны быть осторожны с тем, какой из них вы используете. a += b вызовет __iadd__ и изменит a, тогда как a = a + b создаст новый объект и назначит его a. Это не одна и та же операция!
>>> a1 = a2 = [1, 2] >>> b1 = b2 = [1, 2] >>> a1 += [3] # Uses __iadd__, modifies a1 in-place >>> b1 = b1 + [3] # Uses __add__, creates new list, assigns it to b1 >>> a2 [1, 2, 3] # a1 and a2 are still the same list >>> b2 [1, 2] # whereas only b1 was changed
Для неизменяемых типов (где у вас нет __iadd__) a += b и a = a + b эквивалентны. Это то, что позволяет использовать += для неизменяемых типов, что может показаться странным дизайнерским решением, пока вы не учтете, что в противном случае вы не смогли бы использовать += для неизменяемых типов, таких как numbers!
Ответ 2
Для общего случая см. Ответ Скотта Гриффита. Однако при работе со списками, подобными вашему, += оператор является сокращением от someListObject.extend(iterableObject). Смотрите документацию extend().
Функция extend добавит все элементы параметра в список.
При выполнении foo += something вы изменяете список foo на месте, таким образом, вы не изменяете ссылку, на которую указывает имя foo, но вы изменяете объект list напрямую. С помощью foo = foo + something вы фактически создаете новый список.
Этот пример кода объяснит это:
>>> l = [] >>> id(l) 13043192 >>> l += [3] >>> id(l) 13043192 >>> l = l + [3] >>> id(l) 13059216
Обратите внимание, как изменяется ссылка при переназначении нового списка на l.
Поскольку bar является переменной класса, а не переменной экземпляра, изменение на месте повлияет на все экземпляры этого класса. Но при переопределении self.bar у экземпляра будет отдельная переменная экземпляра self.bar, не затрагивающая другие экземпляры класса.
Ответ 3
Проблема здесь в том, bar определяется как атрибут класса, а не переменная экземпляра.
В foo, атрибут класса изменен в init методе, вот почему затронуты все экземпляры.
В foo2 переменная экземпляра определяется с использованием (пустого) атрибута класса, и каждый экземпляр получает свой собственный bar.
"Правильной" реализацией было бы:
classfoo: def__init__(self, x): self.bar = [x]
Конечно, атрибуты класса полностью легальны. Фактически, вы можете получить к ним доступ и изменять их, не создавая экземпляр класса, подобный этому:
classfoo: bar = []
foo.bar = [x]
Ответ 4
Здесь задействованы две вещи:
1.classattributesand instance attributes 2. difference between the operators + and += for lists
+ оператор вызывает __add__ метод для списка. Он берет все элементы из своих операндов и создает новый список, содержащий эти элементы с сохранением их порядка.
+= оператор вызывает __iadd__ метод в списке. Он принимает iterable и добавляет все элементы iterable в список на месте. Он не создает новый объект list .
В классе foo оператор self.bar += [x] не является оператором присваивания, но фактически преобразуется в
self.bar.__iadd__([x]) # modifies the class attribute
который изменяет список на месте и действует как метод list extend.
В классе foo2, наоборот, оператор присваивания в init методе
self.bar = self.bar + [x]
может быть расшифрован как:
У экземпляра нет атрибута bar (хотя есть атрибут class с тем же именем), поэтому он обращается к атрибуту class bar и создает новый список, добавляя к нему x. Оператор преобразуется в:
self.bar = self.bar.__add__([x]) # bar on the lhs is the class attribute
Затем он создает атрибут экземпляра bar и присваивает ему вновь созданный список. Обратите внимание, что bar в rhs присваивание отличается от bar в lhs.
Для экземпляров класса foo, bar является атрибутом класса, а не экземпляра. Следовательно, любое изменение атрибута класса bar будет отражено для всех экземпляров.
Напротив, каждый экземпляр класса foo2 имеет свой собственный атрибут экземпляра bar, который отличается от атрибута класса с тем же именем bar.
f = foo2(4) print f.bar # accessing the instance attribute. prints [4] print f.__class__.bar # accessing the class attribute. prints []