Как правильно оформлять код с глобальными переменными в Python?
Что проверяют
Понимаете ли вы, как в Python устроены области видимости, и можете ли объяснить, когда глобальное состояние допустимо, а когда его пора убрать в параметры или объект. Следом обычно дают код с UnboundLocalError или спрашивают про потоки.
Как отвечать
Разделите глобальные имена на две группы. Константы на уровне модуля это нормально, изменяемое глобальное состояние это исключение, которое нужно обосновать.
- константы: по PEP 8 пишутся
UPPER_CASEв начале модуля после импортов. АннотацияMAX_RETRIES: Final = 3изtypingне мешает присвоению в рантайме, но mypy и pyright на него ругаются; - внутренние имена: префикс
_показывает, что имя не входит в публичный API модуля, а__all__задаёт, что уйдёт вfrom module import *; global: нужен только для присваивания имени. Мутация объекта (items.append(x)) работает без него. Если в функции есть присваивание безglobal, имя становится локальным во всей функции, и чтение до присваивания даётUnboundLocalError;nonlocal: то же для переменной объемлющей функции в замыкании;- альтернативы: аргументы и возвращаемое значение, класс или
dataclassс состоянием, объект настроек (например, pydantic-settings), внедрение зависимостей. Для данных «на запрос» в асинхронном коде подходитcontextvars.ContextVar; - ленивая инициализация: вместо подключения к базе при импорте модуля используйте функцию с
functools.cacheили фабрику, иначе импорт в тестах полезет в сеть.
Где спотыкаются
Пишут from config import counter и ждут, что изменение config.counter в другом месте будет видно. Импорт копирует ссылку на объект в момент импорта, для изменяемого состояния нужно import config и обращение config.counter.
Считают counter += 1 атомарным «благодаря GIL». Это несколько байткод-операций, между которыми поток может переключиться, так что нужен threading.Lock. В сборках Python без GIL проблема только острее.
Ставят global «на всякий случай» и прячут зависимости функции. Её поведение начинает зависеть от порядка вызовов, а каждый тест требует ручного сброса состояния.