Переменные и память
Переменные другими словами
О переменных в Python вам уже известно многое, вы с ними работали много раз.
- Переменная работает как подписанная коробка или помеченная ячейка, куда можно что-то положить и не потерять:Скопировать кодPYTHON
2 + 2 # Python выполнит этот код, и где-то в памяти будет записана четвёрка. # Но добраться до неё невозможно - и что толку с этой четвёрки. # А вот значение, сохранённое в переменной; оно будет доступно по имени. four = 2 + 2 print('А что у нас в переменной four?', four, 'Так-то.'
- Первое появление переменной в коде называется объявлением переменной;
- при объявлении переменной указывается
- имя переменной,
- знак присваивания
=
, - присваиваемое значение;
- в ходе выполнения программы значение присвоенное переменной может меняться;
- имя переменной придумывает сам программист.
Память и переработка мусора
Есть смысл заглянуть за кулисы и посмотреть, что происходит с переменными в памяти устройства.
Начнём с простого примера:
Скопировать кодPYTHONprint('Unicorn')
После того, как строка напечатана, объект строки
не будет использован повторно: к нему невозможно обратиться для
повторного использования, и Python это отлично понимает.
Как
только объекты становятся не нужны программе — они становятся «мусором»
и автоматически удаляются сборщиком мусора. В Python есть встроенный
неотключаемый механизм для сборки мусора — алгоритм подсчёта ссылок.
Когда объект присваивается переменной, тоже
создаётся ячейка в памяти, но при этом переменная ссылается на эту
ячейку. Сборщик мусора не удаляет такие данные: он видит, что на ячейку
памяти есть ссылка и понимает, что эти данные нужны программе.
Скопировать кодPYTHON# Переменная brand_name типа str имеет значение 'Unicorn'.
brand_name = 'Unicorn'
print(brand_name)
# Вывод в терминал: Unicorn
# Данные, на которые ссылается переменная brand_name,
# всё ещё хранятся в памяти и к ним можно обратиться вновь.
print(brand_name + '!')
# Вывод в терминал: Unicorn!
Переменная указывает на адрес объекта в памяти, в
котором хранятся данные. Адрес блока памяти — это уникальный числовой
идентификатор. К этому идентификатору можно обратиться через встроенную
функцию
id()
.Скопировать кодPYTHON# Объявление переменной типа str
brand_name = 'Unicorn'
# Напечатаем ID той ячейки памяти, на которую ссылается переменная:
print(id(brand_name))
# Вывод в терминал: 25080896
При
выполнении одного и того же кода на разных компьютерах (и даже на одном
компьютере, после перезапуска Python) значение ID ячейки памяти для
одного и того же объекта будет разным.
На один блок памяти могут ссылаться сколько угодно переменных. Например:
Скопировать кодPYTHONbrand_name = 'Unicorn'
print(id(brand_name))
# Вывод в терминал: 25080896
copy_brand_name = brand_name
print(id(copy_brand_name))
# Вывод в терминал: 25080896
# ID совпадают, значит, обе переменные ссылаются на одну ячейку памяти.
Если значение переменной переопределяют — в памяти создаётся новый объект.
Скопировать кодPYTHONbrand_name = 'Unicorn'
print(id(brand_name))
# Вывод в терминал: 25080896
brand_name = 'Cat'
print(id(brand_name))
# Вывод в терминал: 27193914
# ID разные: теперь brand_name ссылается на другую ячейку памяти.
Что случилось?
- Для объекта с новым значением выделена новая ячейка памяти;
- переменная
brand_name
теперь ссылается на новый объект; - объект с прежним значением
'Unicorn'
будет уничтожен сборщиком мусора.Сборщик думает так: «на эту ячейку памяти не ссылается ни одна переменная; значит, обратиться к этому значению из программы невозможно; удаляем этот хлам из памяти».
Сборщик мусора не станет освобождать ячейку памяти, если на эту ячейку есть хотя бы одна ссылка.
Изменяемые и неизменяемые типы данных
В
Python все типы объектов делятся на изменяемые и не изменяемые. Такой
вариант русского перевода может трактоваться неоднозначно. Более
подходящим был бы перевод «мутирующие и немутирующие» типы: это лучше
отражает способность объекта изменять своё содержимое.
Изменяемые типы
Значения изменяемых (англ. mutable)
типов данных хранятся в памяти так, что содержимое ячейки памяти можно
изменять. При изменениях переменная будет ссылаться всё на ту же ячейку
памяти.
Скопировать кодPYTHON
brand_info = ['Unicorn', 2]
print(id(brand_info))
# Вывод в терминал: 34153256
# Изменяем значение элемента списка с индексом 1
brand_info[1] = 3
print(brand_info)
# Вывод в терминал: ['Unicorn', 3]
# Значение объекта изменилось. А что с ID?
print(id(brand_info))
# Вывод в терминал: 34153256
# ID остался прежним.
К изменяемым типам относятся: множества (class
set
), списки (class list
) и словари (class dict
).Неизменяемые типы
Значения неизменяемых (англ. unmutable) типов пишутся в память так, что изменить данные в ячейке памяти невозможно.
При
попытке изменить значение объекта в памяти вернётся ошибка. При
переопределении переменной неизменяемого типа будет создан новый объект в
памяти.
Скопировать кодPYTHONbrand_name = 'Unicorn'
print(id(brand_name))
# Вывод в терминал: 25080896
# При попытки изменить значени элемента строки
# python выведет ошибку
brand_name[1] = 3
# Вывод в терминал:
# TypeError: 'str' object does not support item assignment
# Объект типа строка не поддерживает присвоения значений элементам
Из встроенных типов данных к неизменяемым относятся числа (
int
, float
, complex
), строки (class str
), кортежи (tuple
).Если
какой-то переменной присваивается значение, уже ранее присвоенное
другой переменной, то обе эти переменные будут ссылаться на одну ячейку
памяти. Это справедливо только для неизменяемых типов данных.
Скопировать кодPYTHON# Объявляем переменную неизменяемого типа str
brand_name = 'Unicorn'
print(id(brand_name))
# Вывод в терминал: 25080896
# Присваеваем новой переменной
# то же самое значение
new_brand_name = 'Unicorn'
print(id(new_brand_name))
# Вывод в терминал: 25080896
# Снова тот же ID!
# Объявляем переменную изменяемого типа list
steps_weekends_1 = [13300, 12311]
print(id(steps_weekends_1))
# Вывод в терминал: 38675176
# Присваеваем новой переменной
# то же самое значение
steps_weekends_2 = [13300, 12311]
print(id(steps_weekends_2))
# Вывод в терминал: 38674792
# Значения одинаковые, но ID - разные!
Хешируемые объекты
Для быстрого поиска и сравнения данных, сохранённых в памяти, Python применяет хеширование данных — кодирование, которое преобразует любое значение в целое число.
Значения
неизменяемых типов данных хранятся в памяти в хешированном виде. С
момента создания блока памяти и до его утилизации хеш-значение не может
быть изменено: таковы законы этого мира.
Получить хеш-значение для переменной можно встроенной функцией
hash()
Скопировать кодPYTHONint_hash = hash(5)
print(f'Хеш для числа 5 будет равен {int_hash}')
# Вывод в терминал: Хеш для числа 5 будет равен 5
float_hash = hash(5.123)
print(f'Хеш для числа 5.123 будет равен {float_hash}')
# Вывод в терминал: Хеш для числа 5.123 будет равен 1775969997
string_hash = hash('Строка')
print(f'Хеш для строки "Строка" будет равен {string_hash}')
# Вывод в терминал: Хеш для строки "Строка" будет равен 1577989931
Для изменяемых типов данных функция
hash()
не работает: данные изменяемых типов не хешируются.Скопировать кодPYTHONlist_hash = hash([1,2,3,4,5])
print(f"Хеш для списка [1,2,3,4,5] будет равен {list_hash}")
# Вывод в терминал: TypeError: unhashable type: 'list'
# Все сломалось: списки не хешируются!
Хеши
одинаковых значений, например, хеши двух одинаковых кортежей, будут
равны. Это ускоряет и упрощает поиск и сравнение данных: гораздо проще
сравнивать хеши (целые числа), чем сложные структуры из множества
элементов, которые придётся перебрать.
Поиск
значения по хешам не требует дополнительных действий: чтобы сравнить
два кортежа — нужно сравнить два хеш-значения; чтобы сравнить два списка
— придётся перебрать все их элементы и сравнить попарно.
Выгода очевидна.
Будут ли эти две переменные ссылаться на один объект в памяти?
Скопировать кодPYTHONbrand_name = 'Unicorn'
another_brand_name = 'Unicorn'
Ошибка:
обе эти переменные будут ссылаться на один объект памяти. Так устроен
Python, это механизм рационального использования памяти.
Неизменяемые объекты
- Подходят для безопасного хранения данных (в ходе программы значения не будут изменены, если случайно не переопределить переменную).
- Скорость обращения к неизменяемым объектам для некоторых операций гораздо выше, чем при работе с изменяемыми объектами.
Изменяемые объекты
- При работе с динамически изменяющимися данными объекты изменяемого типа упрощают работу с памятью: не приходится постоянно создавать всё новые и новые объекты в памяти; это экономит время и ресурсы.