Как Python выделяет память под переменную?

«Как Python выделяет память под переменную?» — вопрос из категории Python, который задают на 24% собеседований AQA / Automation. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Внимание: Вопрос относится к Python, но категория — QA/Тестирование. С точки зрения QA-инженера, понимание этого механизма важно для написания корректных тестов, особенно при работе с моками, фикстурами и проверке состояния объектов.

Python использует динамическое управление памятью. Ключевые концепции:

  1. Объекты и ссылки: В Python всё является объектом. Переменная — это ссылка на объект в памяти, а не сам объект.

    a = [1, 2, 3]  # Создаётся объект-список в памяти, `a` становится ссылкой на него
    b = a          # `b` теперь ссылается на ТОТ ЖЕ объект, что и `a`
    b.append(4)
    print(a)  # [1, 2, 3, 4] — изменение через `b` видно через `a`
  2. Куча (Heap): Объекты создаются в области памяти, называемой кучей (heap). Управление этой областью осуществляется интерпретатором Python.

  3. Счётчик ссылок (Reference Counting): Каждый объект имеет счётчик, который увеличивается при создании новой ссылки на него и уменьшается при удалении ссылки. Когда счётчик достигает нуля, память объекта может быть освобождена.

  4. Сборщик мусора (Garbage Collector): Дополнительно к счётчику ссылок существует сборщик мусора, который находит и удаляет объекты, участвующие в циклических ссылках (когда объекты ссылаются друг на друга, но внешних ссылок на них нет).

Практическое значение для QA (на примере тестирования):

import pytest

# НЕПРАВИЛЬНО: Изменение глобального или изменяемого объекта в одном тесте повлияет на другие.
shared_list = []

def test_one():
    shared_list.append(1)
    assert len(shared_list) == 1  # Пройдёт

def test_two():
    # Этот тест зависит от выполнения test_one! Это антипаттерн.
    assert len(shared_list) == 0  # Упадет, если test_one выполнился первым.

# ПРАВИЛЬНО: Каждый тест должен работать с изолированными данными.
# Фикстура pytest создаёт НОВЫЙ объект для каждого теста.
@pytest.fixture
def clean_list():
    return []  # Каждый вызов создаёт новый список в памяти

def test_three(clean_list):
    clean_list.append(1)
    assert len(clean_list) == 1
def test_four(clean_list):
    assert len(clean_list) == 0  # Всегда пройдёт, clean_list — новый объект.

Понимание модели памяти помогает избежать хрупких тестов с неявными зависимостями.