Добавить в список, определенный в кортеже – это ошибка?

Итак, у меня есть этот код:

tup = ([1,2,3],[7,8,9]) tup[0] += (4,5,6) 

который генерирует эту ошибку:

 TypeError: 'tuple' object does not support item assignment 

Хотя этот код:

 tup = ([1,2,3],[7,8,9]) try: tup[0] += (4,5,6) except TypeError: print tup 

печатает это:

 ([1, 2, 3, 4, 5, 6], [7, 8, 9]) 

Ожидается ли такое поведение?

Заметка

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

Да, это ожидается.

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

Поэтому делать что-либо одному из объектов, содержащихся в кортеже, включая добавление к этому объекту, если это список, не относится к семантике кортежа.

(Представьте, если вы написали класс, на котором были методы, из-за чего его внутреннее состояние изменилось. Вы не ожидали, что невозможно будет вызвать эти методы для объекта на основе того, где он хранится).

Или другой пример:

 >>> l1 = [1, 2, 3] >>> l2 = [4, 5, 6] >>> t = (l1, l2) >>> l3 = [l1, l2] >>> l3[1].append(7) 

Два измененных списка, на которые ссылается список и кортеж. Должна ли я выполнить последнюю строку (ответ: да). Если вы считаете, что ответ отрицательный, почему бы и нет? Если t изменить семантику l3 (ответ: нет).

Если вы хотите неизменный объект последовательных структур, он должен быть кортежей полностью вниз.

Почему это ошибка?

В этом примере используется оператор infix:

Многие операции имеют «на месте» версию. Следующие функции обеспечивают более примитивный доступ к операторам на месте, чем обычный синтаксис; например, оператор x + = y эквивалентен x = operator.iadd (x, y). Другой способ сказать, что z = operator.iadd (x, y) эквивалентен составному утверждению z = x; z + = y.

https://docs.python.org/2/library/operator.html

Итак, это:

 l = [1, 2, 3] tup = (l,) tup[0] += (4,5,6) 

эквивалентно этому:

 l = [1, 2, 3] tup = (l,) x = tup[0] x = x.__iadd__([4, 5, 6]) # like extend, but returns x instead of None tup[0] = x 

Линия __iadd__ успешно __iadd__ и изменяет первый список. Таким образом, список был изменен. __iadd__ возвращает измененный список.

Вторая строка пытается присвоить список кортежу, и это не удается.

Итак, в конце программы список был расширен, но вторая часть операции += не удалась. Подробные сведения см. В этом вопросе .

Ну, я думаю, tup[0] += (4, 5, 6) переводится на:

 tup[0] = tup[0].__iadd__((4,5,6)) 

tup[0].__iadd__((4,5,6)) выполняется, обычно меняя список в первом элементе. Но присваивание не выполняется, поскольку кортежи являются неизменяемыми.

Корреспонденты не могут быть изменены напрямую, правильно. Тем не менее, вы можете изменить элемент кортежа по ссылке. Подобно:

 >>> tup = ([1,2,3],[7,8,9]) >>> l = tup[0] >>> l += (4,5,6) >>> tup ([1, 2, 3, 4, 5, 6], [7, 8, 9]) 

Разработчики Python опубликовали официальное сообщение о том, почему это происходит здесь: https://docs.python.org/2/faq/programming.html#why-does-a-tuple-i-item-raise-an-exception-when -The-аддитивная-работа

Краткая версия состоит в том, что + = фактически выполняет две вещи: одну за другой:

  1. Запустите вещь справа.
  2. присвойте результат переменной слева

В этом случае шаг 1 работает, потому что вам разрешено добавлять материал в списки (они изменяемы), но шаг 2 терпит неудачу, потому что вы не можете поместить вещи в кортежи после их создания (кортежи неизменяемы).

В реальной программе я бы предложил вам не делать предложение try-except, потому что tup[0].extend([4,5,6]) делает то же самое.