Какие есть варианты реализации синглтона в Python и их плюсы и минусы
Что проверяют
Понимаете ли вы, как в Python создаётся объект (__new__, __init__, __call__ у метакласса), и можете ли честно назвать цену синглтона. Почти всегда следом спрашивают про потокобезопасность и про то, как такой код тестировать.
Как отвечать
Назовите основные способы и сразу их слабые места:
- модуль: объект создаётся на уровне модуля, импорт кэшируется в
sys.modules, и все получают один экземпляр. Самый питоничный вариант, хотя сам класс по-прежнему можно инстанцировать ещё раз; __new__с атрибутом класса: возвращает сохранённый_instance. Подвох в том, что__init__вызывается при каждомSingleton()и перезаписывает состояние;- декоратор: функция с кэшем экземпляров. Имя класса теперь указывает на функцию, поэтому ломаются
isinstance, наследование и доступ к методам класса; - метакласс: переопределённый
__call__контролирует и создание, и инициализацию, наследники получают поведение автоматически. Цена: код сложнее читать, возможны конфликты с другими метаклассами; - фабрика с
functools.cache: функцияget_settings()создаёт объект при первом вызове. Приём популярен в FastAPI для настроек, а в тестах кэш сбрасывается черезcache_clear().
Про потоки: проверка if cls not in instances сама по себе не атомарна ни в одном варианте. Если создание дорогое и может идти из нескольких потоков, её оборачивают в threading.Lock с двойной проверкой. Модульный вариант безопасен, потому что импорт модуля защищён блокировкой.
Где спотыкаются
Забывают про повторный __init__ в варианте с __new__: объект один, но его поля сбрасываются при каждом «создании».
Называют метакласс гарантией единственности. Без блокировки два потока всё так же могут создать два экземпляра, а в multiprocessing у каждого процесса синглтон свой.
Не видят проблемы для тестов: глобальное состояние протекает из теста в тест. Ответ сильнее, если добавить, что зависимость часто лучше передать явно, например через Depends в FastAPI, а синглтоном оставить то, что правда должно существовать в одном экземпляре, вроде пула соединений.