О переменных и управлении памятью в Python

Alexander Shepetko14 февраля 2016, 11:39 268 0

О том, что же на самом деле происходит, когда вы присваиваете значения переменным в Python и как он всем этим управляет.

Вы когда-нибудь замечали разницу между переменными в C и Python? Например, когда вы выполняете присваивание в C вроде того, как показано ниже, тот выделяет блок в области памяти, который сможет хранить значение переменной:

 int a = 1;

Это можно представить как если бы присваиваемое значение помещалось в некую коробочку с именем переменной на ней:

Таким образом, для каждой новой переменной будет создаваться "коробочка" с именем переменной, которая будет хранить значение. Когда вы меняете значение переменной, вы фактически помещаете в коробочку новое содержимое. То есть, например, когда вы выполните:

 a = 2;

получится нечто вроде этого:

Присваивание одной переменной другой означает создание копии значения из одной коробочки и помещения этой копии в другую коробочку:

 int b = a;

Однако в Python переменные работают немного иначе. Это больше напоминает ярлыки, нежели коробочки, которые мы только что рассмотрели. Когда в Python вы присваиваете значение переменной, он как бы помечает, вешает ярлык с именем переменной на само значение:

 a = 1

И теперь, если вы измените значение переменной, то в реальности произойдёт лишь перевешивание "ярлыка" ан другое значение. Вам не нужно беспокоится об освобождении памяти, отведённой для хранения самого значения; об этом позаботится автоматический сборщик мусора. Когда он обнаруживает в памяти значение, на котором нет ни одного "ярлыка", он удаляет это значение из памяти, таким образом освобождая её.

 a = 2

Присваивание одной переменной другой приводит к тому, что создаётся ещё один "ярлык" и "навешивается" на значение, хранящееся в памяти:

 b = a

Таким образом, то. что в других языках называется "переменные", в Python можно назвать "имена".

Немного об управлении памятью в Python

Как вы видите, значение переменной будет храниться в памяти в единственном экземпляре, а переменные являются на самом деле ссылками на значение. Например, если у вас есть три переменных a, b, и c, которым присвоены значение 10, это не означает, что вы получите в памяти три копии значения. Значение 10 в памяти будет хранится в одном экземпляре, в то время как переменные a, b и c будут указывать на это значение. Когда переменная будет изменена, скажем, операцией a += 1, то в памяти будет выделена новая область, в неё будет помещено значение 11, а переменная a станет указывать на эту область.

Предлагаю проверить всё вышесказанное на практике, в Python Shell.

 >>> a_list = [1] * 10

>>> a_list
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

>>> [id(value) for value in a_list]
[26089400, 26089400, 26089400, 26089400, 26089400, 26089400, 26089400, 26089400, 26089400, 26089400]

id() возвращает уникальный идентификатор объекта (в CPython -- это адрес объекта в памяти). Как мы видим, в созданном нами списке из 10 одинаковых элементов, значение не дублируются в памяти, а используется одно и то же, десять раз. То есть, мы получаем список с десятью ссылками на 1, вместо десяти копий 1.

Давайте попробуем поэкспериментировать с собственным объектом.

 >>> class Foo:
... pass

>>> bar = Foo() 

>>> baz = Foo() 

>>> id(bar)
140730612513248 

>>> id(baz)
140730612513320 

>>> list_a = [1, 2, 3] # списки -- это изменяемые объекты 

>>> list_b = [1, 2, 3] 

>>> id(list_a)
140116233272136

>>> id(list_b)
140116233272064 

>>> [id(value) for value in list_a]
[24221624, 24221600, 24221576] # `int` -- неизменяемый 

>>> [id(value) for value in list_b]
[24221624, 24221600, 24221576] # то же самое, что и в предыдущем списке

Как видите, объекты нашего класса, а также списки (проще говоря, изменяемые (mutable) объекты) имеют разные идентификаторы. Это означает что в памяти имеются две различные области, которые хранят объекты. В то же время неизменяемые (immutable) объекты будут являться ссылками на одну и ту же область в случае одинаковых значений.