July 31, 2018

Python: ссылки или значения?

Как вы думаете, что происходит, когда вы пишете следующее выражение?

a = b

Логичным, однако не совсем корректным ответом, будет "переменной a присваивается значение b". Простой пример:

first = [1, 2, 3]
second = first
second[0] = 5
# first = [5, 2, 3]

Если бы second просто присваивалось значение first, то при изменении second first не менялась бы. Однако это произошло. Cуть в том, что в python все объекты передаются по ссылке(а это объекты класса list). То есть для python всё равно, что вы напишете - first или second - они указывают на одну область в памяти и изменения, сделанные в значении одной переменной, отобразятся и во второй. Однако надо понимать разницу между изменением и присвоением ей нового значения:

first = [1, 2, 3]
second = first
second = [2, 3, 4]
# first не изменился

После присвоения second значения [2, 3, 4] эта переменная теперь указывает на новую область памяти и уже никак не связана с first.

Абсолютно тот же механизм использует передача аргументов в функцию:

def change_value(value):
    value[0] = 10
    value = [3, 4, 5]

a = [1, 2, 3]
change_value(a)
# a = [10, 2, 3]

Первая строчка в функции изменяет значение в памяти, а вторая создаёт ссылку на новую область памяти, а значит связь value с a теряется.

Понимание этого механизма может облегчить вам жизнь при работе с данными в Python.

Например может показаться, что следующий код создаёт двумерный список 3 на 3, заполненный нулями

a = [[0] * 3] * 3  # a = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]

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

a[0][0] = 1  # [[1, 0, 0], [1, 0, 0], [1, 0, 0]]

Нетрудно заметить, что изменились все 3 списка, то есть толку от них мало. "Правильным" созданием такого списка было бы

a = [[0] * 3 for _ in range(3)]

В этом выражении, каждый новый элемент - новая область в памяти, а значит вложенные списки независимы:

a[0][0] = 1  # [[1, 0, 0], [0, 0, 0], [0, 0, 0]]

Надеюсь, теперь вы разобрались в этой теме и не будете совершать ошибок, как в примере с массивом.

Подписывайтесь на группу SnakeBlog