Найти в Дзене
Еще один курс о Python

Загадочный list внутри tuple. Почему и как? Объяснение.

Привет! Продолжаем раскрывать загадки Питона! :)

Как известно, в Python структуры данных делятся на изменяемые, то есть, которые можно изменить in place, при модификации которых происходит изменения данных в имеющимся объекте, и неизменяемые – при попытке модификации в памяти либо создается новый объект, либо бросается исключение.

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

Итак, возьмем типичный кортеж (tuple) – неизменяемый контейнер. После создания такого объекта попытки изменить его будут приводить к ошибке (исключению TypeError). Давайте проверим это:

In [1]: a = tuple([1,2,3])
In [2]: a[0] = 2
TypeError: 'tuple' object does not support item assignment

Да, поведение весьма ожидаемое. Но что будет, если мы положим внутрь неизменяемого кортежа изменяемый список? Возникает дилемма – менять tuple мы не можем, но положить туда ссылку на список и изменять список – да. Давайте скорее проверим!

Скриншот из среды iPython
Скриншот из среды iPython

Кажется, что мы имеем дело с чем-то очень странным! Что мы сделали:

  1. Мы положили список внутри кортежа
  2. Попытались изменить вложенный список, и получили ошибку
  3. Но объект ‘d’ в список все же был добавлен.

Что же произошло? Дело в том, что контейнеры в Python имеют python style специфику – они хранят только ссылки на объекты, но не сами объекты. Поэтому кортеж не может контролировать изменение объектов, ссылки на которые он хранит, он может гарантировать только то, что набор ссылок внутри него не будет изменен.

В данном случае мы успешно изменили состав списка, потому что через tuple получили ссылку на него, и добавили новый элемент. Ошибка же возникла из-за того, что Python распознал обращение к tuple по индексу и операцию += в одной строке, поэтому выбросил исключение. Если же мы воспользуемся методом append, то модификация списка пройдет успешно.

In [8]: a = tuple([1,2, ['a', 'b', 'c']])
In [9]: a[2].append('d')
In [10]: a
Out[10]: (1, 2, ['a', 'b', 'c', 'd'])

Итог – помните, что контейнеры в Python хранят лишь ссылки на объекты и не могут контролировать их поведение, что и было показано в этой статье. Например, можно добавить ссылку на список в это же самый список:

In [12]: a = []
In [13]: a.append(a)
In [14]: a
Out[14]: [[...]]

И Python вполне считает такое поведение валидным. Но применять его на практике я не рекомендую :)

В прошлых статьях я рассказывал, как устроена функция sorted() и почему функция len() работает так быстро – почитайте, это тоже полезно!

А если вы хотите овладеть Python, научиться решать на нем сложные задачи и погрузиться в его строение, приглашаю вас на мой авторский курс, почитать подробнее и записаться можно на сайте myfriendcourse.ru – будет интересно! (и недорого :))