Как работает int с памятью в Python?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как работает int с памятью в Python
Это фундаментальный вопрос о том, как Python управляет памятью для целочисленных объектов. Понимание этого поможет написать более эффективный код.
1. Кеширование малых целых чисел
Python кеширует целые числа от -5 до 256 для оптимизации памяти:
# Кеширует
a = 256
b = 256
print(a is b) # True — один объект в памяти
print(id(a) == id(b)) # True
# Не кеширует
c = 257
d = 257
print(c is d) # False — разные объекты
print(id(c) == id(d)) # False
# Проверим диапазон кеширования
for i in range(-10, 270):
x = i
y = i
if x is not y:
print(f"Cache ends at {i-1}")
break
Почему? Числа -5..256 используются чаще всего в Python программах, поэтому CPython кеширует их для экономии памяти и скорости.
2. Размер объекта int в памяти
Каждый int имеет overhead:
import sys
# Малое число (кешировано)
small_int = 100
print(sys.getsizeof(small_int)) # 28 байт
# Большое число
large_int = 10**18
print(sys.getsizeof(large_int)) # 40+ байт
# Очень большое число
huge_int = 10**1000
print(sys.getsizeof(huge_int)) # 160+ байт
Вывод: чем больше число, тем больше памяти оно занимает. Python использует переменное представление для целых чисел (не фиксированный размер как в C).
3. Внутреннее представление int
Python 3 использует оптимизированное представление для целых чисел:
# Python 3 — неограниченная точность
num = 10**100
print(num) # Работает без проблем
# Представление в памяти (PyLong структура)
# struct PyLongObject {
# PyObject_HEAD
# Py_ssize_t ob_size; // количество цифр
# digit *ob_digit; // массив цифр
# }
# Число хранится как массив 30-битных "цифр"
num = 2**60 # 2 "цифры" (64 бита)
num = 2**61 # 3 "цифры"
4. Кешпроблемы в циклах
Знание о кешировании важно для оптимизации:
import timeit
# Медленно — создание новых объектов для 257+
def slow_func():
total = 0
for i in range(1000):
total += i
return total
# Быстро — переиспользование кешированных объектов
def fast_func():
total = 0
for i in range(-5, 100):
total += i
return total
print(timeit.timeit(slow_func, number=10000))
print(timeit.timeit(fast_func, number=10000))
5. Присваивание и идентичность
# Копирование vs присваивание
a = 100
b = a
print(a is b) # True — указывают на один объект
# Изменение (целые числа неизменяемые)
a = 101
print(a is b) # False — разные объекты
# Почему?
# Целые числа в Python НЕИЗМЕНЯЕМЫЕ (immutable)
# Когда меняешь переменную, создаётся новый объект
x = 5
old_id = id(x)
x += 1 # Это создаёт новый объект!
print(old_id == id(x)) # False
6. Утечки памяти и циклические ссылки
# Циклическая ссылка НЕ утекает для int (они неизменяемые)
class Node:
def __init__(self, value):
self.value = value
self.next = None
# Циклическая ссылка
node1 = Node(1)
node2 = Node(2)
node1.next = node2
node2.next = node1 # Циклическая ссылка
# Python GC соберёт эту память благодаря cycle detection
del node1, node2 # Память освобождена
# Проверка утечек памяти
import gc
# Посмотри, сколько объектов в памяти
print(f"Objects: {len(gc.get_objects())}")
print(f"Referrers of int 100: {len(gc.get_referrers(100))}")
7. Оптимизация использования памяти
# Неоптимально — создание промежуточных объектов
for i in range(1000000):
x = i
y = i + 1
z = i + 2
# Каждую итерацию создаются новые объекты
# Оптимально — переиспользование переменных
x = y = z = 0
for i in range(1000000):
x = i
y = i + 1
z = i + 2
# Меньше переходов в создание новых объектов
# Лучше использовать для больших вычислений
import numpy as np
arr = np.arange(1000000, dtype=np.int64)
# NumPy эффективнее работает с большими коллекциями чисел
8. Сравнение == vs is
# == проверяет значение
a = 257
b = 257
print(a == b) # True
print(a is b) # False — разные объекты!
# is проверяет идентичность (id)
# Используй is только при необходимости проверки идентичности
# Правильно
if x is None: # is для None
pass
# Для чисел используй ==
if x == 100:
pass
9. Профилирование памяти
import tracemalloc
# Начни отслеживание памяти
tracemalloc.start()
# Твой код
numbers = [i for i in range(100000)]
# Получи статистику
current, peak = tracemalloc.get_traced_memory()
print(f"Current: {current / 1024:.1f} KB")
print(f"Peak: {peak / 1024:.1f} KB")
tracemalloc.stop()
10. Практические выводы
- Кешированы числа -5..256 — переиспользуются в памяти
- Большие числа занимают больше памяти — используй numpy для больших массивов
- Целые числа неизменяемые — операции создают новые объекты
- Python GC очищает циклические ссылки — не беспокойся о утечках для int
- Используй == для сравнения значений — is только для None и специальных случаев
- Профилируй при необходимости — tracemalloc и memory_profiler помогут
Помни: оптимизация памяти для целых чисел обычно не критична, если ты не работаешь с миллионами объектов. Фокусируйся на алгоритмической сложности в первую очередь!