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

Если мы берем 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,), ожидая получить тот же результат, но он не сработал.

Ответы [ 7 ]

70 голосов
/ 06 октября 2019

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

«Почему это может работать по-другому?» , на который отвечает, например, this . По существу, += пытается использовать различные методы объекта: __iadd__ (который проверяется только с левой стороны), vs __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 на самом деле не касается эффективности, но эти решения были приняты давно.

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

21 голосов
/ 06 октября 2019

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

b += (4,)

- сокращение для:

b.extend((4,))

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

b = b + (4,)

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

14 голосов
/ 07 октября 2019

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

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 голосов
/ 06 октября 2019

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

s += t
s.extend(t)

определены как:

расширяется s содержимымt

Это отличается от определения как:

s = s + t    # not equivalent in Python!

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

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

s += range(3)
3 голосов
/ 11 октября 2019

«Расширенные» операторы присваивания, такие как +=, были введены в 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 singleton, в этом случае поиск продолжается со следующего шага).

Этот процесс допускает типы, которые поддерживаютмодификация на месте для реализации __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])после выполнения строки, которая вызывает ошибку.

2 голосов
/ 07 октября 2019

Большинство людей ожидают, что 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.

1 голос
/ 18 октября 2019

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

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

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

...