➡️ Два бага из-за области видимости в Python — и как их избежать — 1 июня 2026 г. в 15:48:57.090
➡️ Два бага из-за области видимости в Python — и как их избежать Приложение работает нормально. Открываешь вторую вкладку — данные не те. Тесты проходят по отдельности, но падают вместе. Причина — объект на уровне модуля. Проблема 1: слишком много sharing Типичный паттерн в FastAPI-проектах: # database.py engine = create_engine("sqlite:///database.db") engine создаётся в момент импорта. Все модули, которые импортируют из database.py , используют один и тот же engine, один пул соединений, один файл БД. Тесты чистят базу через setup/teardown, но состояние всё равно утекает: автоинкремент ID, кэш метаданных, внутреннее состояние SQLite. Тест assert id == 1 проходит первым, но падает если запустить после другого теста. Фикс — создавать engine на каждый тест: @pytest.fixture def engine(): engine = create_engine("sqlite://", echo=False) SQLModel.metadata.create_all(engine) yield engine engine.dispose() In-memory БД, никакого shared state, никакой уборки. Проблема 2: слишком мало sharing Обратная ситуация. Зависимость FastAPI создаёт новый экземпляр на каждый запрос: def get_data_source() -> DataSource: return FakeDataSource(...) # новый объект каждый раз Каждая вкладка браузера получает свой FakeDataSource со своим состоянием с нуля. Вкладка А показывает одно, вкладка Б — другое. Никакой общей реальности нет. Фикс — app.state: @asynccontextmanager async def lifespan(app: FastAPI): app.state.data_source = get_data_source() yield def get_data_source(request: Request) -> DataSource: return request.app.state.data_source Один экземпляр на весь процесс, легко подменяется в тестах через dependency_overrides. Правило одной строкой Если объект хранит мутабельное состояние — выбирай его скоуп осознанно. Слишком широкий (модуль) — тесты текут друг в друга. Слишком узкий (каждый запрос) — нет общего состояния. Константы и чистые значения на уровне модуля — ок. Ресурсы (DB engine, HTTP клиент, кэш, пул соединений) — нет.

