+=
Оператор в питона , кажется, работает неожиданно в списках. Кто-нибудь может сказать мне, что здесь происходит?
class foo:
bar = []
def __init__(self,x):
self.bar += [x]
class foo2:
bar = []
def __init__(self,x):
self.bar = self.bar + [x]
f = foo(1)
g = foo(2)
print f.bar
print g.bar
f.bar += [3]
print f.bar
print g.bar
f.bar = f.bar + [4]
print f.bar
print g.bar
f = foo2(1)
g = foo2(2)
print f.bar
print g.bar
ВЫВОД
[1, 2]
[1, 2]
[1, 2, 3]
[1, 2, 3]
[1, 2, 3, 4]
[1, 2, 3]
[1]
[2]
foo += bar
похоже, влияет на каждый экземпляр класса, тогда как, foo = foo + bar
похоже, ведет себя так, как я ожидал, что все будет вести себя.
+=
Оператор называется «соединение оператор присваивания».
python
augmented-assignment
eucalculia
источник
источник
+
оператор для массивов. Я думаю, что в этом случае есть смысл+=
добавить.Ответы:
Общий ответ заключается в том, что он
+=
пытается вызвать__iadd__
специальный метод, а если он недоступен, он пытается использовать__add__
вместо него. Итак, проблема в различии этих специальных методов.Этот
__iadd__
специальный метод предназначен для добавления на месте, то есть он изменяет объект, на который действует.__add__
Специальный метод возвращает новый объект , а также используется для стандартного+
оператора.Поэтому, когда
+=
оператор используется для объекта, который имеет__iadd__
определенный объект, изменяется на месте. В противном случае вместо этого он попытается использовать простой__add__
и вернуть новый объект.Вот почему для изменяемых типов, таких как списки,
+=
изменяется значение объекта, тогда как для неизменяемых типов, таких как кортежи, строки и целые числа, вместо этого возвращается новый объект (a += b
становится эквивалентнымa = a + b
).Для типов, которые поддерживают и то,
__iadd__
и другое__add__
, поэтому вы должны быть осторожны при выборе из них.a += b
вызовет__iadd__
и изменитa
, тогда какa = a + b
создаст новый объект и назначит егоa
. Это не одна и та же операция!Для неизменяемых типов (где у вас их нет
__iadd__
)a += b
иa = a + b
эквивалентны. Это то, что позволяет вам использовать+=
для неизменяемых типов, что может показаться странным дизайнерским решением, пока вы не решите, что в противном случае вы не могли бы использовать+=
для неизменных типов, таких как числа!источник
__radd__
метод, который иногда может вызываться (это актуально для выражений, которые в основном включают подклассы).+=
самом деле список расширяется , это объясняет, почемуx = []; x = x + {}
даетTypeError
времяx = []; x += {}
просто возвращается[]
.Для общего случая см . Ответ Скотта Гриффита . Однако при работе со списками, как вы,
+=
оператор является сокращением дляsomeListObject.extend(iterableObject)
. См. Документацию по extend () .extend
Функция будет добавлять все элементы параметра в списке.При этом
foo += something
выfoo
изменяете список на месте, таким образом, вы не меняете ссылку, на которуюfoo
указывает имя , а напрямую меняете объект списка. С помощьюfoo = foo + something
вы фактически создаете новый список.Этот пример кода объяснит это:
Обратите внимание, как изменяется ссылка, когда вы переназначаете новый список
l
.Как
bar
и переменная класса вместо переменной экземпляра, изменение на месте повлияет на все экземпляры этого класса. Но при переопределенииself.bar
экземпляр будет иметь отдельную переменную экземпляра,self.bar
не влияя на другие экземпляры класса.источник
a += b
это отличается отa = a + b
двух списковa
иb
. Но в этом есть смысл;extend
будет чаще предполагаться для работы со списками, а не для создания новой копии всего списка, которая будет иметь более высокую временную сложность. Если разработчикам нужно быть осторожными, чтобы не изменять исходные списки на месте, то кортежи - лучший вариант в качестве неизменяемых объектов.+=
с кортежами не может изменять исходный кортеж.Проблема здесь в том, что
bar
он определяется как атрибут класса, а не как переменная экземпляра.В методе
foo
модифицируется атрибут классаinit
, поэтому затрагиваются все экземпляры.В
foo2
переменная экземпляра определяется с помощью (пустого) атрибута класса, и каждый экземпляр получает свой собственныйbar
.«Правильная» реализация будет:
Конечно, атрибуты класса полностью законны. Фактически, вы можете получить к ним доступ и изменить, не создавая экземпляр класса следующим образом:
источник
Здесь задействованы две вещи:
+
Оператор вызывает__add__
метод из списка. Он берет все элементы из своих операндов и составляет новый список, содержащий эти элементы, сохраняя их порядок.+=
оператор вызывает__iadd__
метод из списка. Он принимает итерацию и добавляет все элементы итерации в список на месте. Он не создает новый объект списка.В классе
foo
операторself.bar += [x]
не является оператором присваивания, а фактически переводится каккоторый изменяет список на месте и действует как метод списка
extend
.В классе
foo2
, наоборот, оператор присваивания вinit
методеможет быть деконструирован следующим образом:
У экземпляра нет атрибута
bar
( хотя есть атрибут класса с тем же именем), поэтому он обращается к атрибуту классаbar
и создает новый список, добавляяx
к нему. Заявление переводится как:Затем он создает атрибут экземпляра
bar
и назначает ему вновь созданный список. Обратите внимание, чтоbar
правая сторона присвоения отличается отbar
левой.Для экземпляров класса
foo
,bar
является атрибутом класса , а не атрибут экземпляра. Следовательно, любое изменение атрибута классаbar
будет отражено для всех экземпляров.Напротив, каждый экземпляр класса
foo2
имеет свой собственный атрибут экземпляра,bar
который отличается от атрибута класса с тем же именемbar
.Надеюсь, это проясняет ситуацию.
источник
Хотя прошло много времени и было сказано много правильных вещей, нет ответа, объединяющего оба эффекта.
У вас есть 2 эффекта:
+=
(как заявил Скотт Гриффитс )В классе
foo
, то__init__
метод изменяет атрибут класса. Это потому, чтоself.bar += [x]
переводится какself.bar = self.bar.__iadd__([x])
.__iadd__()
предназначен для модификации на месте, поэтому он изменяет список и возвращает ссылку на него.Обратите внимание, что dict экземпляра изменяется, хотя обычно в этом нет необходимости, поскольку dict класса уже содержит такое же присвоение. Так что эта деталь остаётся почти незамеченной - если только вы не сделаете это
foo.bar = []
позже. Здесь количество экземпляровbar
остается неизменным благодаря указанному факту.В классе
foo2
, однако, классbar
используются, но не трогали. Вместо этого[x]
к нему добавляется, образуя новый объект, какself.bar.__add__([x])
здесь называется, который не изменяет объект. Затем результат помещается в экземпляр dict, давая экземпляру новый список как dict, в то время как атрибут класса остается измененным.Различие между
... = ... + ...
и... += ...
влияет также на последующие назначения:Вы можете проверить идентичность объектов с помощью
print id(foo), id(f), id(g)
(не забудьте дополнительные()
s, если вы используете Python3).Кстати:
+=
оператор называется «расширенным назначением» и обычно предназначен для внесения изменений на месте, насколько это возможно.источник
Другие ответы, казалось бы, в значительной степени охватили это, хотя, похоже, стоит процитировать и сослаться на расширенные назначения PEP 203 :
...
источник
источник
Мы видим, что когда мы пытаемся изменить неизменяемый объект (в данном случае целое число), Python вместо этого просто предоставляет нам другой объект. С другой стороны, мы можем вносить изменения в изменяемый объект (список) и оставлять его неизменным во всем.
ссылка: https://medium.com/@tyastropheus/tricky-python-i-memory-management-for-mutable-immutable-objects-21507d1e5b95
Также обратитесь к URL-адресу ниже, чтобы понять мелкую и глубокую копию.
https://www.geeksforgeeks.org/copy-python-deep-copy-shallow-copy/
источник