Почему b+ = (4,) работает, а b = b + (4,) не работает, когда b является списком?

Если мы возьмем b = [1,2,3] и попробуем сделать: b+=(4,)

Он возвращает b = [1,2,3,4], но если мы попытаемся сделать b = b + (4,), это не сработает.

b = [1,2,3]
b+=(4,) # Prints out b = [1,2,3,4]
b = b + (4,) # Gives an error saying you can't add tuples and lists

Я ожидал, что b+=(4,) потерпит неудачу, поскольку вы не можете добавить список и кортеж, но это сработало. Поэтому я попытался b = b + (4,), ожидая получить тот же результат, но это не сработало.

Ответы

Ответ 1

Проблема с вопросами "почему" заключается в том, что обычно они могут означать несколько разных вещей. Я постараюсь ответить на каждый вопрос, который, я думаю, вы могли иметь в виду.

"Почему это может работать по-другому?", на который отвечает, например, этот. По сути, += пытается использовать различные методы объекта: __iadd__ (который проверяется только с левой стороны), против __add__ и __radd__ ("обратное добавление", проверяется с правой стороны). сторона, если левая сторона не имеет __add__) для +.

"Что именно делает каждая версия?" Короче говоря, метод list.__iadd__ делает то же самое, что и list.extend (но из-за языкового дизайна все еще существует задание обратно).

Это также означает, например, что

>>> a = [1,2,3]
>>> b = a
>>> a += [4] # uses the .extend logic, so it is still the same object
>>> b # therefore a and b are still the same list, and b has the '4' added
[1, 2, 3, 4]
>>> b = b + [5] # makes a new list and assigns back to b
>>> a # so now a is a separate list and does not have the '5'
[1, 2, 3, 4]

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

"Почему это полезно для + =? Это более эффективно; метод extend не должен создавать новый объект. Конечно, иногда это имеет некоторые неожиданные эффекты (как выше), и, как правило, Python на самом деле речь идет не об эффективности, но эти решения были приняты давно.

"По какой причине нельзя разрешать добавление списков и кортежей с помощью +?" Смотрите здесь (спасибо, @splash58); одна идея состоит в том, что (tuple + list) должен производить тот же тип, что и (list + tuple), и неясно, каким типом должен быть результат. += не имеет этой проблемы, потому что a += b, очевидно, не должен изменять тип a.

Ответ 2

Они не эквивалентны:

b += (4,)

это сокращение от:

b.extend((4,))

в то время как + объединяет списки, поэтому:

b = b + (4,)

вы пытаетесь объединить кортеж в список

Ответ 3

Когда вы делаете это:

b += (4,)

преобразуется в это:

b.__iadd__((4,)) 

он вызывает b.extend((4,))

extend принимает итератор, и поэтому это также работает:

b = [1,2,3]
b += range(2)  # prints [1, 2, 3, 0, 1]

но когда вы это сделаете:

b = b + (4,)

преобразуется в это:

b = b.__add__((4,)) 

Здесь есть некоторые ограничения, этот метод принимает только список объектов

Ответ 4

Из официальных документов для типов изменяемой последовательности оба:

s += t
s.extend(t)

определяются как:

дополняет s содержимым t

Который отличается от того, что определяется как:

s = s + t    # not equivalent in Python!

Это также означает, что любой тип последовательности будет работать для t, включая кортеж, как в вашем примере.

Но это также работает для диапазонов и генераторов! Например, вы также можете сделать:

s += range(3)

Ответ 5

"Расширенные" операторы присваивания, такие как +=, были введены в Python 2.0, выпущенном в октябре 2000 года. Дизайн и обоснование описаны в PEP 203. Одной из заявленных целей этих операторов была поддержка операций на месте. Написание

a = [1, 2, 3]
a += [4, 5, 6]

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

Однако эта операция не всегда может происходить на месте, поскольку многие типы Python, включая целые числа и строки, являются неизменяемыми, например, i += 1 для целого числа i не может работать на месте.

Таким образом, операторы расширенного присваивания должны были работать, когда это возможно, и создавать новый объект в противном случае. Чтобы облегчить достижение этих целей проектирования, было указано, что выражение x += y ведет себя следующим образом:

  • Если определено x.__iadd__, оценивается x.__iadd__(y).
  • В противном случае, если реализован x.__add__, оценивается x.__add__(y).
  • В противном случае, если реализован y.__radd__, оценивается y.__radd__(x).
  • В противном случае выведите ошибку.

Первый результат, полученный в результате этого процесса, будет возвращен x (если только этот результат не является одноэлементным NotImplemented, и в этом случае поиск продолжается со следующего шага).

Этот процесс позволяет типам, которые поддерживают модификацию на месте, реализовать __iadd__(). Типы, которые не поддерживают модификацию на месте, не нуждаются в добавлении каких-либо новых магических методов, поскольку Python автоматически возвращается к x = x + y.

Итак, позвольте, наконец, перейти к вашему актуальному вопросу - почему вы можете добавить кортеж в список с помощью оператора расширенного присваивания. По памяти история этого была примерно такой: метод list.__iadd__() был реализован для простого вызова уже существующего метода list.extend() в Python 2.0. Когда в Python 2.1 были представлены итераторы, метод list.extend() был обновлен, чтобы принимать произвольные итераторы. Конечным результатом этих изменений было то, что my_list += my_tuple работал начиная с Python 2.1. Однако метод list.__add__() никогда не предполагал поддержку произвольных итераторов в качестве правого аргумента - это считалось неуместным для строго типизированного языка.

Лично я считаю, что реализация расширенных операторов оказалась слишком сложной в Python. У него много неожиданных побочных эффектов, например, этот код:

t = ([42], [43])
t[0] += [44]

Вторая строка вызывает TypeError: 'tuple' object does not support item assignment, но операция все равно успешно выполняется - t будет ([42, 44], [43]) после выполнения строки, которая выдает ошибку.

Ответ 6

Большинство людей ожидают, что X + = Y будет эквивалентно X = X + Y. Действительно, Справочник по Python Pocket Reference (4-е издание) Марка Латца говорит на странице 57 "Следующие два формата примерно эквивалентны: X = X + Y, X + = Y ". Однако люди, которые указали Python, не сделали их эквивалентными. Возможно, это было ошибкой, которая приведет к часам отладки разочарованными программистами до тех пор, пока Python остается в использовании, но теперь он так же, как Python. Если X является изменяемым типом последовательности, X + = Y эквивалентно X.extend(Y), а не X = X + Y.

Ответ 7

Как объяснялось здесь, если array не реализует метод __iadd__, b+=(4,) будет просто кратким из b = b + (4,), но, очевидно, это не так, поэтому array действительно реализует __iadd__ метод. По-видимому, реализация метода __iadd__ выглядит примерно так:

def __iadd__(self, x):
    self.extend(x)

Однако мы знаем, что приведенный выше код не является реальной реализацией метода __iadd__, но мы можем предположить и принять, что существует что-то вроде метода extend, который принимает входные данные tupple.