Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Ссылки и изменяемость Python: Списки

На прошлом уроке я ввёл понятие ссылки и упомянул, что в Пайтоне всё и всегда передаётся по ссылке. Поэкспериментируем со списком, как с первым известным нам изменяемым объектом. Но для начала нужно узнать о паре полезных для наших экспериментов инструментов — функции id и операторе is.

id и is

Если обратиться к описанию функции (help(id)), то документация скажет:

id(obj, /)
    Return the identity of an object.

    This is guaranteed to be unique among simultaneously existing objects.

Функция id возвращает уникальный идентификатор объекта, который вы ей передаёте в качестве аргумента (помним, по ссылке!). Идентификатор — это обычное число. Но каждый отдельный объект имеет уникальный идентификатор, т.е. любые два разных объекта всегда будут иметь отличающиеся идентификаторы. И пусть идентификаторы не сохраняются от одного запуска Python к другому, но в рамках одного запуска связь объекта и идентификатора нерушима. Поэтому идентификаторы удобно использовать чтобы отслеживать передачи ссылок на объект между разными участками кода — идентификатор объекта будет одним и тем же, по какой бы ссылке мы к объекту ни обращались.

a = "some string"
b = a
id(a)  # 139739990935280
id(b)  # 139739990935280
print(a is b)  # => True

Когда мы "присваиваем значение одной переменной другой", фактически создаётся новая именованная ссылка на исходное значение. Поэтому id(a) и id(b) возвращают одинаковый результат.

Оператор is проверяет равенство идентификаторов своих операндов. В данном примере обе переменные ссылаются на один объект, поэтому проверка a is b даёт True.

Проверка на равенство идентификаторов — очень быстрая. И особенно удобно ей пользоваться, когда мы имеем дело с так называемыми объектами-одиночками (singleton objects). Самые известные одиночки в Python, это True, False и None. Поэтому проверка на равенство None обычно пишется так:

...
if foo is None:
    ...

Списки, кортежи и ссылки

Посмотрите на этот пример:

a = [1, 2, 3]
b = a
a.append(4)
print(b)  # => [1, 2, 3, 4]

Что мы видим — поменяли "список a", а изменился ещё и "список b"! В действительности же нет никаких двух списков, но есть две ссылки на один!

Продолжим:

a = []
l = [a, a]
a.append(1)
print(l)  # => [[1], [1]]

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

А помните, я говорил, что кортеж не может изменяться? А как вам такое:

a = []
pair = (a, a)
pair[0].append(1)
pair[1].append(2)
print(pair)  # => ([1, 2], [1, 2])

Значение в кортеже поменялось?! И всё же я говорил правду: настоящее содержимое кортежа — это ссылки на значения. И эти ссылки меняться не могут. Но могут меняться сами объекты по этим ссылкам!

Ещё интереснее наблюдать за списками и кортежами, создаваемыми с помощью специального синтаксиса — умножения списка или кортежа на число. Если умножить список или кортеж на число n, то мы получим новую коллекцию соответствующего типа, состоящую из n повторов элементов исходной коллекции. Вот несколько примеров:

print([1, 2, 3] * 3)  # => [1, 2, 3, 1, 2, 3, 1, 2, 3]
print(('foo', 'bar') * 2)  # => ('foo', 'bar', 'foo', 'bar')
print([[]] * 5)  # => [[], [], [], [], []]
print(((),) * 5)  # => ((), (), (), (), ())

Теперь, если мы вспомним, что коллекции — всегда коллекции ссылок, то можно будет догадаться о том, как такие "растиражированные" коллекции будут себя вести при изменении изменяемых элементов. Смотрите:

t = ([], [], []) * 3
print(t)  # => ([], [], [], [], [], [], [], [], [])
t[0].append(42)
t[1].append(0)
print(t)  # => ([42], [0], [], [42], [0], [], [42], [0], [])

Запустите REPL и воспроизведите пример, а затем с помощью id и is посмотрите, какие элементы кортежа на какие объекты ссылаются.

Ссылки и присваивание

Мы увидели, что в список можно добавить несколько ссылок на один объект. И что переменные — те же ссылки, просто именованные.

Но что происходит с переменными и элементами списка при присваивании? Посмотрим:

a = "foo"
id(a)  # 139739990954536
a += "bar"
print(a)  # => 'foobar'
id(a)  # 139739952783688

Этот пример показывает, что имя переменной не жёстко связано со ссылкой на значение. Присваивание переменной (+= — это вид присваивания) может поменять одну ссылку на другую. Это свойство присуще и элементам списка:

a = "foo"
l = [a, a]
print(l[0] is l[1])  # => True
l[0] += "bar"
print(l)  # => ['foobar', 'foo']
print(l[0] is l[1])  # => False

Здесь сначала два элемента списка ссылаются на одно значение. Но после присваивания нового значения первому элементу, связь элемента с изначальным значением разрывается, и под конец элементы ссылаются на разные значения.


Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты.

Ошибки, сложный материал, вопросы >
Нашли опечатку или неточность?

Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.

Что-то не получается или материал кажется сложным?

Загляните в раздел «Обсуждение»:

  • задайте вопрос. Вы быстрее справитесь с трудностями и прокачаете навык постановки правильных вопросов, что пригодится и в учёбе, и в работе программистом;
  • расскажите о своих впечатлениях. Если курс слишком сложный, подробный отзыв поможет нам сделать его лучше;
  • изучите вопросы других учеников и ответы на них. Это база знаний, которой можно и нужно пользоваться.

Об обучении на Хекслете

Для полного доступа к курсу нужен базовый план

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

Получить доступ
900
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.

  • 130 курсов, 2000+ часов теории
  • 900 практических заданий в браузере
  • 360 000 студентов
Даю согласие на обработку персональных данных, соглашаюсь с «Политикой конфиденциальности» и «Условиями оказания услуг»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы

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

Иконка программы Python-разработчик
Профессия
Разработка веб-приложений на Django
18 мая 10 месяцев

Используйте Хекслет по максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Даю согласие на обработку персональных данных, соглашаюсь с «Политикой конфиденциальности» и «Условиями оказания услуг»