Python: a + = b не то же самое, что a = a + b
Возможный дубликат:
Что означает плюс (+ =) в Python?
Сегодня я нашел интересную "особенность" языка python, которая сильно огорчила меня.
>>> a = [1, 2, 3]
>>> b = "lol"
>>> a = a + b
TypeError: can only concatenate list (not "str") to list
>>> a += b
>>> a
[1, 2, 3, 'l', 'o', 'l']
Как это? Я думал, что эти два должны быть эквивалентны! Хуже того, это код, который я чертовски отлаживал
>>> a = [1, 2, 3]
>>> b = {'omg': 'noob', 'wtf' : 'bbq'}
>>> a = a + b
TypeError: can only concatenate list (not "dict") to list
>>> a += b
>>> a
[1, 2, 3, 'omg', 'wtf']
WTF! У меня были списки и дикты в моем коде, и мне было интересно, как, черт возьми, я закончил добавление ключей моего dict в список, не вызывающий .keys(). Как оказалось, вот как.
Я думал, что оба утверждения должны быть эквивалентными. Даже игнорируя это, я могу понять, как вы добавляете строки в списки (поскольку строки - это только массивы символов), но словари? Возможно, если он добавит список (ключ, значение) кортежей, но захват только ключей для добавления в список кажется совершенно произвольным.
Кто-нибудь знает логику этого?
Ответы
Ответ 1
Это и всегда было проблемой с изменчивостью в целом и перегрузкой оператора. С++ не лучше.
Выражение a + b
вычисляет новый список из объектов, привязанных к a
и b
, которые не изменяются. Когда вы присвойте это обратно a
, вы измените привязку одной переменной, чтобы указать на новое значение. Ожидается, что +
является симметричным, поэтому вы не можете добавить dict и список.
Оператор a += b
изменяет существующий список, связанный с a
. Поскольку он не изменяет идентификатор объекта, изменения видны для всех привязок к объекту, представленному a
. Оператор +=
, очевидно, не симметричен, он эквивалентен list.extend
, который итерации над вторым операндом. Для словарей это означает перечисление ключей.
Обсуждение:
Если объект не реализует +=
, тогда Python переведет его в эквивалентный оператор, используя +
и =
. Таким образом, они иногда эквивалентны, в зависимости от типа объектов.
Преимущество +=
, которое мутирует референт (в отличие от значения операнда, которое является ссылкой), заключается в том, что реализация может быть более эффективной без соответствующего увеличения сложности реализации.
В других языках вы можете использовать более понятные обозначения. Например, в гипотетической версии Python без перегрузки оператора вы можете увидеть:
a = concat(a, b)
против
a.extend(a, b)
Обозначение оператора на самом деле просто сокращает их.
Bonus:
Попробуйте использовать и другие итерации.
>>> a = [1,2,3]
>>> b = "abc"
>>> a + b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only concatenate list (not "str") to list
>>> a += b
>>> a
[1, 2, 3, 'a', 'b', 'c']
Полезно иметь возможность сделать это, потому что вы можете добавить генератор в список с помощью +=
и получить содержимое генератора. К сожалению, он нарушает совместимость с +
, но хорошо.
Ответ 2
Причина этого в том, что списки python (a
в вашем случае) реализуют метод __iadd__
, который по очереди вызывает метод __iter__
для переданного параметра.
Следующий фрагмент кода иллюстрирует это лучше:
class MyDict(dict):
def __iter__(self):
print "__iter__ was called"
return super(MyDict, self).__iter__()
class MyList(list):
def __iadd__(self, other):
print "__iadd__ was called"
return super(MyList, self).__iadd__(other)
a = MyList(['a', 'b', 'c'])
b = MyDict((('d1', 1), ('d2', 2), ('d3', 3)))
a += b
print a
Результат:
__iadd__ was called
__iter__ was called
['a', 'b', 'c', 'd2', 'd3', 'd1']
интерпретатор python проверяет, реализует ли объект операцию __iadd__
(+=
), и только если она не будет ее эмулировать, выполняя операцию добавления, за которой следует назначение.