В мире Python существует много мифов о том, как работают переменные. Одни говорят, что "всё передаётся по ссылке", другие утверждают обратное. Правда, как обычно, лежит где-то посередине и гораздо интереснее простых объяснений. В этой статье мы детально разберём механизмы работы с памятью в Python 3.13, изучим различия между mutable и immutable объектами, и поймём, когда Python создаёт новые объекты, а когда переиспользует существующие. Дабы статье пожить подольше - рассмотрю только версию 3.13.
Фундаментальные концепции: всё есть объект
Начнём с самого важного принципа Python: всё является объектом. Когда мы пишем:
pythonx = 42
x = 42
Мы не создаём переменную x
, которая содержит значение 42. Вместо этого происходит следующее:
Python создаёт объект типа
int
со значением 42 в куче (heap)Создаётся имя
x
в пространстве имён (namespace)Имя
x
связывается с объектом через ссылку
Это принципиальное отличие от языков вроде C, где переменная - это именованная область памяти, содержащая значение.
Исследуем объекты изнутри
Каждый объект в Python имеет три обязательных атрибута:
pythonx = 42
print(f"Значение: {x}") # 42
print(f"Тип: {type(x)}") # <class 'int'>
print(f"ID (адрес): {id(x)}") # Уникальный идентификатор в памяти
print(f"Размер: {x.__sizeof__()}") # Размер в байтах
x = 42
print(f"Значение: {x}") # 42
print(f"Тип: {type(x)}") # <class 'int'>
print(f"ID (адрес): {id(x)}") # Уникальный идентификатор в памяти
print(f"Размер: {x.__sizeof__()}") # Размер в байтах
ID объекта - это его адрес в памяти (в CPython). Этот механизм позволяет понять, когда мы работаем с одним и тем же объектом:
pythona = 1000
b = 1000
print(id(a) == id(b)) # Может быть False!
a = 5
b = 5
print(id(a) == id(b)) # True - интернирование малых чисел
a = 1000
b = 1000
print(id(a) == id(b)) # Может быть False!
a = 5
b = 5
print(id(a) == id(b)) # True - интернирование малых чисел
Архитектура памяти Python: многоуровневая система
Python использует сложную систему управления памятью, состоящую из нескольких уровней:
1. Системный уровень (malloc/free)
На самом низком уровне Python взаимодействует с системными функциями выделения памяти. Однако прямое обращение к malloc/free было бы неэффективно для множества мелких объектов.
2. Менеджер памяти Python (PyMalloc)
Python реализует собственный аллокатор памяти, оптимизированный для работы с объектами размером до 512 байт:
pythonimport sys
# Информация о состоянии менеджера памяти
def memory_info():
import gc
print(f"Количество объектов: {len(gc.get_objects())}")
print(f"Статистика GC: {gc.get_stats()}")
# В Python 3.13 добавлены новые методы мониторинга
if hasattr(sys, 'getallocatedblocks'):
print(f"Выделено блоков: {sys.getallocatedblocks()}")
import sys
# Информация о состоянии менеджера памяти
def memory_info():
import gc
print(f"Количество объектов: {len(gc.get_objects())}")
print(f"Статистика GC: {gc.get_stats()}")
# В Python 3.13 добавлены новые методы мониторинга
if hasattr(sys, 'getallocatedblocks'):
print(f"Выделено блоков: {sys.getallocatedblocks()}")
3. Объектные аллокаторы
Каждый тип объекта может иметь свой специализированный аллокатор:
Integers: кеширование малых чисел (-5 до 256)
Strings: интернирование строк
Lists: предварительное выделение места для роста
Dicts: оптимизированные структуры с Python 3.6+
Интернирование и кеширование: оптимизации под капотом
Кеширование малых целых чисел
Python предварительно создаёт объекты для чисел от -5 до 256:
python# Демонстрация кеширования
def demonstrate_int_caching():
# Малые числа всегда ссылаются на один объект
a = 100
b = 100
print(f"a is b: {a is b}") # True
print(f"id(a): {id(a)}")
print(f"id(b): {id(b)}")
# Большие числа могут создавать новые объекты
x = 1000
y = 1000
print(f"x is y: {x is y}") # Обычно False
print(f"id(x): {id(x)}")
print(f"id(y): {id(y)}")
demonstrate_int_caching()
# Демонстрация кеширования
def demonstrate_int_caching():
# Малые числа всегда ссылаются на один объект
a = 100
b = 100
print(f"a is b: {a is b}") # True
print(f"id(a): {id(a)}")
print(f"id(b): {id(b)}")
# Большие числа могут создавать новые объекты
x = 1000
y = 1000
print(f"x is y: {x is y}") # Обычно False
print(f"id(x): {id(x)}")
print(f"id(y): {id(y)}")
demonstrate_int_caching()
Интернирование строк
Python автоматически интернирует строки, похожие на идентификаторы:
pythondef demonstrate_string_interning():
# Автоматическое интернирование
s1 = "hello"
s2 = "hello"
print(f"s1 is s2: {s1 is s2}") # True
# Строки с пробелами могут не интернироваться
s3 = "hello world"
s4 = "hello world"
print(f"s3 is s4: {s3 is s4}") # Может быть False
# Принудительное интернирование
import sys
s5 = sys.intern("hello world")
s6 = sys.intern("hello world")
print(f"s5 is s6: {s5 is s6}") # True
demonstrate_string_interning()
def demonstrate_string_interning():
# Автоматическое интернирование
s1 = "hello"
s2 = "hello"
print(f"s1 is s2: {s1 is s2}") # True
# Строки с пробелами могут не интернироваться
s3 = "hello world"
s4 = "hello world"
print(f"s3 is s4: {s3 is s4}") # Может быть False
# Принудительное интернирование
import sys
s5 = sys.intern("hello world")
s6 = sys.intern("hello world")
print(f"s5 is s6: {s5 is s6}") # True
demonstrate_string_interning()
Mutable vs Immutable: ключевое различие
Понимание разницы между изменяемыми (mutable) и неизменяемыми (immutable) объектами критично для работы с Python.
Immutable объекты
К неизменяемым относятся: int
, float
, str
, tuple
, frozenset
, bytes
:
pythondef immutable_example():
# Создаём строку
original = "Hello"
modified = original + " World"
print(f"original: {original}") # Hello
print(f"modified: {modified}") # Hello World
print(f"Same object: {original is modified}") # False
# "Изменение" создаёт новый объект
number = 42
print(f"ID before: {id(number)}")
number += 1 # Создаётся новый объект!
print(f"ID after: {id(number)}")
print(f"Value: {number}")
immutable_example()
def immutable_example():
# Создаём строку
original = "Hello"
modified = original + " World"
print(f"original: {original}") # Hello
print(f"modified: {modified}") # Hello World
print(f"Same object: {original is modified}") # False
# "Изменение" создаёт новый объект
number = 42
print(f"ID before: {id(number)}")
number += 1 # Создаётся новый объект!
print(f"ID after: {id(number)}")
print(f"Value: {number}")
immutable_example()
Mutable объекты
К изменяемым относятся: list
, dict
, set
, пользовательские классы (по умолчанию):
pythondef mutable_example():
# Список изменяется на месте
original_list = [1, 2, 3]
list_id_before = id(original_list)
original_list.append(4) # Изменение существующего объекта
list_id_after = id(original_list)
print(f"List: {original_list}") # [1, 2, 3, 4]
print(f"Same object: {list_id_before == list_id_after}") # True
# Словари тоже изменяемы
d = {"a": 1}
dict_id_before = id(d)
d["b"] = 2
dict_id_after = id(d)
print(f"Dict same object: {dict_id_before == dict_id_after}") # True
mutable_example()
def mutable_example():
# Список изменяется на месте
original_list = [1, 2, 3]
list_id_before = id(original_list)
original_list.append(4) # Изменение существующего объекта
list_id_after = id(original_list)
print(f"List: {original_list}") # [1, 2, 3, 4]
print(f"Same object: {list_id_before == list_id_after}") # True
# Словари тоже изменяемы
d = {"a": 1}
dict_id_before = id(d)
d["b"] = 2
dict_id_after = id(d)
print(f"Dict same object: {dict_id_before == dict_id_after}") # True
mutable_example()
Передача аргументов: детальный анализ
В Python всё передаётся по ссылке на объект (object reference). Но поведение зависит от того, изменяемый объект или нет.
Передача immutable объектов
pythondef modify_immutable(x):
print(f"Получен объект с ID: {id(x)}")
x = x + 10 # Создаётся новый объект
print(f"После изменения ID: {id(x)}")
return x
original = 42
print(f"Исходный ID: {id(original)}")
result = modify_immutable(original)
print(f"Исходное значение: {original}") # 42 - не изменилось
print(f"Результат: {result}") # 52
def modify_immutable(x):
print(f"Получен объект с ID: {id(x)}")
x = x + 10 # Создаётся новый объект
print(f"После изменения ID: {id(x)}")
return x
original = 42
print(f"Исходный ID: {id(original)}")
result = modify_immutable(original)
print(f"Исходное значение: {original}") # 42 - не изменилось
print(f"Результат: {result}") # 52
Передача mutable объектов
pythondef modify_mutable(lst):
print(f"Получен список с ID: {id(lst)}")
lst.append(4) # Изменяем существующий объект
print(f"После добавления ID: {id(lst)}") # ID не изменился
lst = [100, 200] # Создаём новый объект и переназначаем ссылку
print(f"После переназначения ID: {id(lst)}") # Новый ID
return lst
original_list = [1, 2, 3]
print(f"Исходный ID: {id(original_list)}")
result = modify_mutable(original_list)
print(f"Исходный список: {original_list}") # [1, 2, 3, 4] - изменился!
print(f"Возвращённый список: {result}") # [100, 200]
def modify_mutable(lst):
print(f"Получен список с ID: {id(lst)}")
lst.append(4) # Изменяем существующий объект
print(f"После добавления ID: {id(lst)}") # ID не изменился
lst = [100, 200] # Создаём новый объект и переназначаем ссылку
print(f"После переназначения ID: {id(lst)}") # Новый ID
return lst
original_list = [1, 2, 3]
print(f"Исходный ID: {id(original_list)}")
result = modify_mutable(original_list)
print(f"Исходный список: {original_list}") # [1, 2, 3, 4] - изменился!
print(f"Возвращённый список: {result}") # [100, 200]
Продвинутые сценарии: когда интуиция подводит
Множественное присваивание
pythondef multiple_assignment_analysis():
# Случай 1: immutable объекты
a = b = c = [1, 2, 3] # Все ссылаются на один список!
print(f"a is b: {a is b}") # True
print(f"b is c: {b is c}") # True
a.append(4)
print(f"После изменения a: {a}") # [1, 2, 3, 4]
print(f"b тоже изменился: {b}") # [1, 2, 3, 4]
print(f"c тоже изменился: {c}") # [1, 2, 3, 4]
# Правильный способ создания независимых списков
a = [1, 2, 3]
b = [1, 2, 3]
c = [1, 2, 3]
# Или используя copy
a = [1, 2, 3]
b = a.copy()
c = list(a)
multiple_assignment_analysis()
def multiple_assignment_analysis():
# Случай 1: immutable объекты
a = b = c = [1, 2, 3] # Все ссылаются на один список!
print(f"a is b: {a is b}") # True
print(f"b is c: {b is c}") # True
a.append(4)
print(f"После изменения a: {a}") # [1, 2, 3, 4]
print(f"b тоже изменился: {b}") # [1, 2, 3, 4]
print(f"c тоже изменился: {c}") # [1, 2, 3, 4]
# Правильный способ создания независимых списков
a = [1, 2, 3]
b = [1, 2, 3]
c = [1, 2, 3]
# Или используя copy
a = [1, 2, 3]
b = a.copy()
c = list(a)
multiple_assignment_analysis()
Замыкания и мутабельные объекты
pythondef closure_trap():
functions = []
# Неправильно - все функции ссылаются на одну переменную
for i in [1, 2, 3]:
functions.append(lambda: i) # i изменяется!
print("Неправильный подход:")
for f in functions:
print(f()) # Напечатает 3, 3, 3
# Правильно - создаём локальную копию
functions_correct = []
for i in [1, 2, 3]:
functions_correct.append(lambda x=i: x) # Захватываем значение
print("Правильный подход:")
for f in functions_correct:
print(f()) # Напечатает 1, 2, 3
closure_trap()
def closure_trap():
functions = []
# Неправильно - все функции ссылаются на одну переменную
for i in [1, 2, 3]:
functions.append(lambda: i) # i изменяется!
print("Неправильный подход:")
for f in functions:
print(f()) # Напечатает 3, 3, 3
# Правильно - создаём локальную копию
functions_correct = []
for i in [1, 2, 3]:
functions_correct.append(lambda x=i: x) # Захватываем значение
print("Правильный подход:")
for f in functions_correct:
print(f()) # Напечатает 1, 2, 3
closure_trap()
Специальные случаи и оптимизации в Python 3.13
JIT-компиляция и управление памятью
Python 3.13.3 is the latest release, packed with a Just-in-Time (JIT) compiler, который влияет на работу с объектами:
pythondef jit_optimization_example():
# JIT может оптимизировать создание объектов в циклах
def hot_function(n):
result = []
for i in range(n):
# JIT может оптимизировать создание int объектов
result.append(i * 2)
return result
# При многократном вызове JIT оптимизирует код
for _ in range(1000):
hot_function(100)
jit_optimization_example()
def jit_optimization_example():
# JIT может оптимизировать создание объектов в циклах
def hot_function(n):
result = []
for i in range(n):
# JIT может оптимизировать создание int объектов
result.append(i * 2)
return result
# При многократном вызове JIT оптимизирует код
for _ in range(1000):
hot_function(100)
jit_optimization_example()
Улучшения в многопоточности
В Python 3.13 улучшена работа с памятью в многопоточных приложениях:
pythonimport threading
import time
def thread_memory_example():
shared_data = {"counter": 0}
def worker():
# Каждый поток работает с одним объектом
for _ in range(100000):
shared_data["counter"] += 1 # Изменяем объект на месте
threads = []
for _ in range(4):
t = threading.Thread(target=worker)
threads.append(t)
t.start()
for t in threads:
t.join()
print(f"Final counter: {shared_data['counter']}")
# Результат может быть непредсказуемым без синхронизации!
# thread_memory_example() # Раскомментируйте для тестирования
import threading
import time
def thread_memory_example():
shared_data = {"counter": 0}
def worker():
# Каждый поток работает с одним объектом
for _ in range(100000):
shared_data["counter"] += 1 # Изменяем объект на месте
threads = []
for _ in range(4):
t = threading.Thread(target=worker)
threads.append(t)
t.start()
for t in threads:
t.join()
print(f"Final counter: {shared_data['counter']}")
# Результат может быть непредсказуемым без синхронизации!
# thread_memory_example() # Раскомментируйте для тестирования
Профилирование памяти: инструменты и техники
Встроенные инструменты
pythonimport sys
import gc
from collections import defaultdict
def memory_profiling():
# Подсчёт объектов по типам
def count_objects():
counts = defaultdict(int)
for obj in gc.get_objects():
counts[type(obj).__name__] += 1
return dict(counts)
print("Объекты в памяти:")
initial_counts = count_objects()
# Создаём много объектов
data = []
for i in range(1000):
data.append({"id": i, "value": f"item_{i}"})
final_counts = count_objects()
# Сравниваем изменения
for obj_type in set(initial_counts.keys()) | set(final_counts.keys()):
initial = initial_counts.get(obj_type, 0)
final = final_counts.get(obj_type, 0)
if final > initial:
print(f"{obj_type}: +{final - initial}")
memory_profiling()
import sys
import gc
from collections import defaultdict
def memory_profiling():
# Подсчёт объектов по типам
def count_objects():
counts = defaultdict(int)
for obj in gc.get_objects():
counts[type(obj).__name__] += 1
return dict(counts)
print("Объекты в памяти:")
initial_counts = count_objects()
# Создаём много объектов
data = []
for i in range(1000):
data.append({"id": i, "value": f"item_{i}"})
final_counts = count_objects()
# Сравниваем изменения
for obj_type in set(initial_counts.keys()) | set(final_counts.keys()):
initial = initial_counts.get(obj_type, 0)
final = final_counts.get(obj_type, 0)
if final > initial:
print(f"{obj_type}: +{final - initial}")
memory_profiling()
Использование slots для экономии памяти
pythonclass RegularClass:
def __init__(self, x, y):
self.x = x
self.y = y
class SlottedClass:
__slots__ = ['x', 'y']
def __init__(self, x, y):
self.x = x
self.y = y
def slots_comparison():
# Создаём экземпляры
regular = RegularClass(1, 2)
slotted = SlottedClass(1, 2)
print(f"Regular class size: {sys.getsizeof(regular) + sys.getsizeof(regular.__dict__)}")
print(f"Slotted class size: {sys.getsizeof(slotted)}")
# __slots__ экономит память, но ограничивает функциональность
try:
regular.z = 3 # Работает
print("Regular: можно добавлять атрибуты")
except:
pass
try:
slotted.z = 3 # Ошибка!
print("Slotted: можно добавлять атрибуты")
except AttributeError as e:
print(f"Slotted: {e}")
slots_comparison()
class RegularClass:
def __init__(self, x, y):
self.x = x
self.y = y
class SlottedClass:
__slots__ = ['x', 'y']
def __init__(self, x, y):
self.x = x
self.y = y
def slots_comparison():
# Создаём экземпляры
regular = RegularClass(1, 2)
slotted = SlottedClass(1, 2)
print(f"Regular class size: {sys.getsizeof(regular) + sys.getsizeof(regular.__dict__)}")
print(f"Slotted class size: {sys.getsizeof(slotted)}")
# __slots__ экономит память, но ограничивает функциональность
try:
regular.z = 3 # Работает
print("Regular: можно добавлять атрибуты")
except:
pass
try:
slotted.z = 3 # Ошибка!
print("Slotted: можно добавлять атрибуты")
except AttributeError as e:
print(f"Slotted: {e}")
slots_comparison()
Распространённые ошибки и их избежание
Ошибка 1: Мутация во время итерации
pythondef iteration_mutation_error():
# НЕПРАВИЛЬНО
items = [1, 2, 3, 4, 5]
for item in items:
if item % 2 == 0:
items.remove(item) # Изменяем список во время итерации!
print(f"Результат (неправильно): {items}") # Может быть не то, что ожидаете
# ПРАВИЛЬНО - создаём новый список
items = [1, 2, 3, 4, 5]
items = [item for item in items if item % 2 != 0]
print(f"Результат (правильно): {items}")
# ПРАВИЛЬНО - итерируем по копии
items = [1, 2, 3, 4, 5]
for item in items[:]: # Создаём копию для итерации
if item % 2 == 0:
items.remove(item)
print(f"Результат (альтернатива): {items}")
iteration_mutation_error()
def iteration_mutation_error():
# НЕПРАВИЛЬНО
items = [1, 2, 3, 4, 5]
for item in items:
if item % 2 == 0:
items.remove(item) # Изменяем список во время итерации!
print(f"Результат (неправильно): {items}") # Может быть не то, что ожидаете
# ПРАВИЛЬНО - создаём новый список
items = [1, 2, 3, 4, 5]
items = [item for item in items if item % 2 != 0]
print(f"Результат (правильно): {items}")
# ПРАВИЛЬНО - итерируем по копии
items = [1, 2, 3, 4, 5]
for item in items[:]: # Создаём копию для итерации
if item % 2 == 0:
items.remove(item)
print(f"Результат (альтернатива): {items}")
iteration_mutation_error()
Ошибка 2: Неглубокое копирование
pythondef shallow_copy_trap():
original = [[1, 2], [3, 4]]
# Поверхностное копирование
shallow = original.copy()
shallow[0].append(3) # Изменяем вложенный список
print(f"Original: {original}") # [[1, 2, 3], [3, 4]] - изменился!
print(f"Shallow: {shallow}") # [[1, 2, 3], [3, 4]]
# Глубокое копирование
import copy
original = [[1, 2], [3, 4]]
deep = copy.deepcopy(original)
deep[0].append(3)
print(f"Original after deep copy: {original}") # [[1, 2], [3, 4]] - не изменился
print(f"Deep copy: {deep}") # [[1, 2, 3], [3, 4]]
shallow_copy_trap()
def shallow_copy_trap():
original = [[1, 2], [3, 4]]
# Поверхностное копирование
shallow = original.copy()
shallow[0].append(3) # Изменяем вложенный список
print(f"Original: {original}") # [[1, 2, 3], [3, 4]] - изменился!
print(f"Shallow: {shallow}") # [[1, 2, 3], [3, 4]]
# Глубокое копирование
import copy
original = [[1, 2], [3, 4]]
deep = copy.deepcopy(original)
deep[0].append(3)
print(f"Original after deep copy: {original}") # [[1, 2], [3, 4]] - не изменился
print(f"Deep copy: {deep}") # [[1, 2, 3], [3, 4]]
shallow_copy_trap()
Оптимизация производительности
Предварительное выделение памяти
pythondef memory_preallocation():
import time
# Неэффективно - множественные расширения списка
def slow_way(n):
result = []
for i in range(n):
result.append(i)
return result
# Эффективно - предварительное выделение
def fast_way(n):
result = [None] * n
for i in range(n):
result[i] = i
return result
# Ещё эффективнее - генератор
def fastest_way(n):
return list(range(n))
n = 100000
start = time.time()
slow_result = slow_way(n)
slow_time = time.time() - start
start = time.time()
fast_result = fast_way(n)
fast_time = time.time() - start
start = time.time()
fastest_result = fastest_way(n)
fastest_time = time.time() - start
print(f"Медленный способ: {slow_time:.4f}s")
print(f"Быстрый способ: {fast_time:.4f}s")
print(f"Самый быстрый: {fastest_time:.4f}s")
memory_preallocation()
def memory_preallocation():
import time
# Неэффективно - множественные расширения списка
def slow_way(n):
result = []
for i in range(n):
result.append(i)
return result
# Эффективно - предварительное выделение
def fast_way(n):
result = [None] * n
for i in range(n):
result[i] = i
return result
# Ещё эффективнее - генератор
def fastest_way(n):
return list(range(n))
n = 100000
start = time.time()
slow_result = slow_way(n)
slow_time = time.time() - start
start = time.time()
fast_result = fast_way(n)
fast_time = time.time() - start
start = time.time()
fastest_result = fastest_way(n)
fastest_time = time.time() - start
print(f"Медленный способ: {slow_time:.4f}s")
print(f"Быстрый способ: {fast_time:.4f}s")
print(f"Самый быстрый: {fastest_time:.4f}s")
memory_preallocation()
Использование генераторов для экономии памяти
pythondef generator_memory_efficiency():
# Список - создаёт все объекты в памяти
def list_approach(n):
return [x**2 for x in range(n)]
# Генератор - создаёт объекты по требованию
def generator_approach(n):
return (x**2 for x in range(n))
n = 1000000
# Измеряем использование памяти
import sys
list_result = list_approach(n)
print(f"Размер списка: {sys.getsizeof(list_result)} байт")
gen_result = generator_approach(n)
print(f"Размер генератора: {sys.getsizeof(gen_result)} байт")
# Генератор использует во много раз меньше памяти!
generator_memory_efficiency()
def generator_memory_efficiency():
# Список - создаёт все объекты в памяти
def list_approach(n):
return [x**2 for x in range(n)]
# Генератор - создаёт объекты по требованию
def generator_approach(n):
return (x**2 for x in range(n))
n = 1000000
# Измеряем использование памяти
import sys
list_result = list_approach(n)
print(f"Размер списка: {sys.getsizeof(list_result)} байт")
gen_result = generator_approach(n)
print(f"Размер генератора: {sys.getsizeof(gen_result)} байт")
# Генератор использует во много раз меньше памяти!
generator_memory_efficiency()
Новые возможности Python 3.13
Улучшенная отладка с цветными трейсбеками
colorized tracebacks помогают лучше понимать проблемы с памятью:
pythondef colorized_traceback_example():
def problematic_function():
# Создаём проблемную ситуацию для демонстрации
large_data = [i for i in range(1000000)]
# Попытка доступа к несуществующему индексу
return large_data[2000000] # IndexError
try:
problematic_function()
except IndexError as e:
print(f"Поймали ошибку: {e}")
# В Python 3.13 трейсбек будет цветным и более информативным
# colorized_traceback_example() # Раскомментируйте для тестирования
def colorized_traceback_example():
def problematic_function():
# Создаём проблемную ситуацию для демонстрации
large_data = [i for i in range(1000000)]
# Попытка доступа к несуществующему индексу
return large_data[2000000] # IndexError
try:
problematic_function()
except IndexError as e:
print(f"Поймали ошибку: {e}")
# В Python 3.13 трейсбек будет цветным и более информативным
# colorized_traceback_example() # Раскомментируйте для тестирования
Практические рекомендации
1. Мониторинг использования памяти
pythondef memory_monitoring():
import psutil
import os
process = psutil.Process(os.getpid())
def get_memory_usage():
return process.memory_info().rss / 1024 / 1024 # МБ
print(f"Использование памяти в начале: {get_memory_usage():.2f} МБ")
# Создаём много объектов
data = []
for i in range(100000):
data.append({"index": i, "square": i**2, "data": f"item_{i}"})
print(f"После создания объектов: {get_memory_usage():.2f} МБ")
# Очищаем ссылки
del data
import gc
gc.collect()
print(f"После очистки: {get_memory_usage():.2f} МБ")
# memory_monitoring() # Требует psutil
def memory_monitoring():
import psutil
import os
process = psutil.Process(os.getpid())
def get_memory_usage():
return process.memory_info().rss / 1024 / 1024 # МБ
print(f"Использование памяти в начале: {get_memory_usage():.2f} МБ")
# Создаём много объектов
data = []
for i in range(100000):
data.append({"index": i, "square": i**2, "data": f"item_{i}"})
print(f"После создания объектов: {get_memory_usage():.2f} МБ")
# Очищаем ссылки
del data
import gc
gc.collect()
print(f"После очистки: {get_memory_usage():.2f} МБ")
# memory_monitoring() # Требует psutil
2. Оптимизация структур данных
pythondef data_structure_optimization():
# Сравнение различных способов хранения данных
import array
# Обычный список
regular_list = [i for i in range(1000)]
print(f"Обычный список: {sys.getsizeof(regular_list)} байт")
# Array - более эффективен для числовых данных
int_array = array.array('i', range(1000))
print(f"Array: {sys.getsizeof(int_array)} байт")
# Bytes - ещё эффективнее для байтовых данных
if all(0 <= x <= 255 for x in range(100)):
byte_data = bytes(range(100))
print(f"Bytes: {sys.getsizeof(byte_data)} байт")
data_structure_optimization()
def data_structure_optimization():
# Сравнение различных способов хранения данных
import array
# Обычный список
regular_list = [i for i in range(1000)]
print(f"Обычный список: {sys.getsizeof(regular_list)} байт")
# Array - более эффективен для числовых данных
int_array = array.array('i', range(1000))
print(f"Array: {sys.getsizeof(int_array)} байт")
# Bytes - ещё эффективнее для байтовых данных
if all(0 <= x <= 255 for x in range(100)):
byte_data = bytes(range(100))
print(f"Bytes: {sys.getsizeof(byte_data)} байт")
data_structure_optimization()
Заключение
Понимание того, как Python управляет памятью и объектами, критично для написания эффективного кода. Основные выводы:
Всё является объектом - Python создаёт объекты в куче и работает со ссылками на них
Различайте mutable и immutable - это определяет поведение при передаче в функции
Используйте оптимизации - интернирование строк, кеширование чисел, slots для классов
Мониторьте память - особенно важно в долгоживущих приложениях
Изучайте новые возможности - Python 3.13 принёс JIT-компиляцию и улучшения в многопоточности
Современный Python становится всё более производительным, но знание основ остаётся ключом к написанию качественного кода. Надеюсь, эта статья поможет вам лучше понимать, что происходит "под капотом" вашего Python-кода.
Если у вас есть вопросы или дополнения - пишите в комментариях. Делитесь своими кейсами и примерами оптимизации памяти в Python!
Комментарии (5)
avshkol
17.06.2025 17:21# Большие числа могут создавать новые объекты x = 1000 y = 1000 print(f"x is y: {x
is
y}") # Обычно False
Вот это интересно: когда могут, а когда не могут?
Alex283
17.06.2025 17:21ужас, что только не придумают в новых языках программированиях, чтобы программа работала еще медленее, чем у её предшественников!
DmitriyReztsov
17.06.2025 17:21Под конец статья сдулась, если честно. Но спасибо за быстрый обзор и напоминание. Как шпаргалка начинающим подойдет. Только надо переформатировать блоки кода - у меня они дублируются и криво печатаются.
nikolz
Можете объяснить, что нового Вы рассказали относительно учебников из интернета?
Не лучший пересказ того, что есть в интернете.
Например это:
https://pythonchik.ru/osnovy/peremennye-v-python
https://skillbox.ru/media/code/peremennye-v-python-chto-eto-takoe-i-kakie-oni-byvayut/
--------------------
Вот Алиса Яндекса рассказывает и дает ссылки на первоисточники :
Внутреннее представление переменных в языке Python заключается в том, что переменная хранит не само значение, а ссылку на его адрес в памяти. Это связано с динамической типизацией языка: значения присваиваются переменным не при компиляции, а во время выполнения программы. skillbox.rupracticum.yandex.ru
Структура переменных
Переменная состоит из трёх частей:
Имя (идентификатор) — название, придуманное программистом для обращения к переменной.
Значение — информация, которая хранится в памяти компьютера и с которой работает программа. Значение всегда принадлежит к какому-либо типу данных.
Адрес — номер ячейки памяти, в которой хранится значение переменной.
skillbox.ru
Тип переменной определяется исходя из значения, которое ей присвоено. Например, при присвоении строки в двойных или одинарных кавычках переменная имеет тип
, а при присвоении целого числа —
. metanit.com
Примеры кода
Чтобы продемонстрировать внутреннее представление переменных, можно использовать функцию
. Она возвращает уникальный идентификатор объекта, на который ссылается переменная. skillbox.rudevpractice.rupythonchik.ru
Пример:
В этом примере при инициализации переменной
программа создала объект с числом 4, принадлежащий к классу
. Внутри переменной содержится не сам объект, а его адрес. skillbox.ru