Чому b + = (4,) працює, а b = b + (4,) не працює, коли b є списком?


77

Якщо ми беремо 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,)очікувати отримання такого ж результату, але це не вийшло.


4
Я вірю, що відповідь можна знайти тут .
jochen


Спочатку я це неправильно прочитав і спробував закрити як занадто широкий, потім відкликав. Тоді я подумав, що це повинен бути дублікат, але не тільки я не міг повторно проголосувати, я витягнув волосся, намагаючись знайти інші відповіді на кшталт цих. : /
Карл

Дуже схоже запитання: stackoverflow.com/questions/58048664 / ...
sanyash

Відповіді:


70

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

"Чому можливо, щоб це працювало інакше?" на що відповідає, наприклад, це . В основному, +=намагається використовувати різні методи об’єкта: __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 насправді не стосується ефективності , але ці рішення були прийняті давно.

"Яка причина не дозволяти додавати списки та кортежі з +?" Дивіться тут (спасибі, @ splash58); одна ідея полягає в тому, що (кортеж + список) повинен створювати той самий тип, що і (список + кортеж), і незрозуміло, для якого типу повинен бути результат. +=не має цієї проблеми, тому що a += bочевидно не повинно змінювати тип a.


2
Думаю, цілком правильно. І списки не використовуються |, так що якимось чином руйнує мій приклад. Якщо я
Карл

1
Btw |для наборів є комуніруючим оператором, але +для списків - ні. З цієї причини я не думаю, що аргумент щодо двозначності типу є особливо сильним. Оскільки оператор не коментує, навіщо вимагати те саме для типів? Можна просто погодитися, що результат має тип lhs. З іншого боку, обмежуючи list + iteratorрозробника, рекомендується більш чітко говорити про свої наміри. Якщо ви хочете створити новий список , який містить матеріал , з aпродовжену матеріалу з bуже є спосіб зробити це: new = a.copy(); new += b. Це ще одна лінія, але кришталево чиста.
a_guest

Причина, по якій a += bповодиться інакше, ніж a = a + bне є ефективність. Насправді Гвідо вважав обрану поведінку менш заплутаною. Уявіть функцію, яка отримує список aяк аргумент, а потім виконує a += [1, 2, 3]. Цей синтаксис, безумовно, схоже, що це зміна списку на місці, а не створення нового списку, тому було прийнято рішення, що він повинен вести себе відповідно до інтуїції більшості людей щодо очікуваного результату. Однак механізм також повинен був працювати для таких непорушних типів, як ints, що призвело до існуючої конструкції.
Свен Марнах

Я особисто думаю, що дизайн насправді більш заплутаний, ніж просто створення a += bскорочень a = a + b, як це робив Рубі, але я можу зрозуміти, як ми туди потрапили.
Sven Marnach

21

Вони не рівнозначні:

b += (4,)

це скорочення для:

b.extend((4,))

в той час як +об'єднує списки, таким чином:

b = b + (4,)

ви намагаєтесь об'єднати кортеж у список


14

Коли ви це зробите:

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)

3

Такі «розширені» оператори присвоєння, як, наприклад, +=були представлені в 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])після виконання рядка, який викликає помилку.


Браво! Особливо корисно мати посилання на ПЕП. Я додав посилання на іншому кінці, до попереднього запитання про те, що стосується поведінки в списку. Коли я оглядаюсь на те, яким був Python раніше 2.3 або близько того, це здається практично непридатним у порівнянні з сьогоднішнім днем ​​... (і я все ще маю туманну пам’ять про спроби і не в змозі отримати 1,5, щоб зробити щось корисне на дуже старому Mac)
Карл

2

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


> Можливо, це була помилка, яка призведе до годин налагодження розчарованими програмістами до тих пір, поки Python залишається у використанні <- ви насправді страждали через це? Ви, здається, говорите з досвіду. Я дуже хотів би почути вашу історію.
Векі

1

Як пояснено тут , якщо arrayне реалізувати __iadd__метод, це b+=(4,)буде просто скороченим, b = b + (4,)але очевидно, що це не так, як arrayі __iadd__метод реалізації . Мабуть, реалізація __iadd__методу приблизно така:

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

Однак ми знаємо, що наведений вище код не є реальною реалізацією __iadd__методу, але ми можемо припустити і прийняти, що існує щось на зразок extendметоду, який приймає tuppleвведення.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.