Кортежи: + = оператор выдает исключение, но преуспевает?
Почему следующие исключения вызывают исключение, хотя это удается?
>>> t = ([1, 2, 3], 4)
>>> t[0] += [1]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> t
([1, 2, 3, 1], 4)
>>>
Ответы
Ответ 1
Найден ответ на IRC.
t[0] += [1]
- несколько дискретных действий:
- Загрузка
t[0]
- создание нового списка с
1
в нем
- добавив, что
[1]
для t[0]
- переназначение
t[0]
Кажется, что x += y
в основном x = x + y
(но, это?)
Сложный бит заключается в том, что +=
подразумевает назначение как кортежа t
, так и списка t[0]
t[0] += [1]
не буквально t[0] = t[0] + [1]
, это: t[0] = t[0].__iadd__([1])
Что действительно происходит:
-
__iadd__
оба мутируют список и возвращают его. Таким образом, список (который является первым элементом в t
) уже добавил к нему 1
.
- мутация типа tuple также выполняется на месте, но кортежи неизменяемы, что приводит к исключению.
Почему это не видно на виду? Поскольку n00b, подобный мне, ожидал бы, что t[0] += [1]
либо сработает вместе, либо потерпит неудачу, потому что это одна короткая строка python. Но это не всегда так.
Ответ 2
Это также поможет понять это поведение, взглянув на байт-код с dis.dis
.
In[5]: dis('t[0] += [1]')
1 0 LOAD_NAME 0 (t)
3 LOAD_CONST 0 (0)
6 DUP_TOP_TWO
7 BINARY_SUBSCR
8 LOAD_CONST 1 (1)
11 BUILD_LIST 1
14 INPLACE_ADD
15 ROT_THREE
16 STORE_SUBSCR
17 LOAD_CONST 2 (None)
20 RETURN_VALUE
- Значение
t[0]
помещается поверх стека с BINARY_SUBSCR
, который является (изменяемым) списком в этом случае.
- Значение в верхней части стека имеет
+= [1]
, выполняемое на нем с INPLACE_ADD
, где в этом случае верхняя часть стека относится к списку внутри кортежа.
- Назначение
t[0]
в начало стека происходит с помощью STORE_SUBSCR
, который здесь не выполняется, поскольку t
сам является неизменяемым кортежем, что вызывает ошибку после того, как назначение +=
уже произошло.
Ответ 3
Разработчики Python опубликовали официальное объяснение, почему это происходит здесь: https://docs.python.org/2/faq/programming.html#why-does-a-tuple-i-item-raise-an-exception-when-the-addition-works
Короткий вариант состоит в том, что + = фактически выполняет две вещи: одну за другой:
- Возьмите вещь справа и добавьте ее в переменную слева
- Поместите результат в переменную слева
В этом случае шаг 1 работает, потому что вам разрешено добавлять материал в списки (theyre mutable), но шаг 2 терпит неудачу, потому что вы не можете положить материал в кортежи после их создания (кортежи неизменяемы).
В реальной программе я предлагаю вам не делать этого, потому что t [0].extend(['c']) делает то же самое.