Подтвердить что ты не робот

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(). Как оказалось, вот как.

Я думал, что оба утверждения должны быть эквивалентными. Даже игнорируя это, я могу понять, как вы добавляете строки в списки (поскольку строки - это только массивы символов), но словари? Возможно, если он добавит список (ключ, значение) кортежей, но захват только ключей для добавления в список кажется совершенно произвольным.

Кто-нибудь знает логику этого?

4b9b3361

Ответ 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__ (+=), и только если она не будет ее эмулировать, выполняя операцию добавления, за которой следует назначение.