На прошлом уроке мы ввели понятие «ссылки» и упомянули, что в 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
.
Проверка на равенство идентификаторов — очень быстрая. И особенно удобно ей пользоваться, когда мы имеем дело с так называемыми объектами-одиночками.
Самые известные одиночки в 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], [])
Мы увидели, что в список можно добавить несколько ссылок на один объект. И что переменные — те же ссылки, просто именованные.
Но что происходит с переменными и элементами списка при присваивании? Посмотрим:
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
Здесь сначала два элемента списка ссылаются на одно значение. Но после присваивания нового значения первому элементу, связь элемента с изначальным значением разрывается, и под конец элементы ссылаются на разные значения.
Вам ответят команда поддержки Хекслета или другие студенты.
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
Наши выпускники работают в компаниях:
С нуля до разработчика. Возвращаем деньги, если не удалось найти работу.
Зарегистрируйтесь или войдите в свой аккаунт