Можно ли на уровне модуля сделать класс синглтоном и запретить импортировать сам класс, только экземпляр в Python
Что проверяют
Знаете ли вы, что модуль в Python уже работает как синглтон, и понимаете ли, что жёстко запретить импорт имени язык не позволяет. Следом обычно спрашивают, чем плох классический синглтон через __new__ и как быть с потоками.
Как отвечать
Сделать экземпляр единственным просто. Модуль исполняется один раз при первом импорте и кэшируется в sys.modules, поэтому объект, созданный на уровне модуля, будет общим для всех, кто его импортирует:
class _Settings:
def __init__(self):
self.debug = False
settings = _Settings()
__all__ = ["settings"]Инициализация при этом потокобезопасна: импорт защищён блокировкой, два потока не выполнят модуль дважды.
Скрыть класс полностью нельзя, есть только соглашения:
- подчёркивание:
_Settingsпомечает имя как внутреннее,from module import *его не заберёт, но явныйfrom module import _Settingsсработает; __all__: влияет только на импорт со звёздочкой, от явного импорта не защищает;del _Settings: имя из модуля пропадёт, ноtype(settings)всё равно вернёт класс, иtype(settings)()создаст второй экземпляр;- защита от повторного создания: если это критично, класс проверяет в
__new__флаг и бросает исключение на второй вызов.
Сильный ответ добавляет вывод: в Python доступ ограничивают договорённостями, а не средствами языка. Если нужен ленивый экземпляр, проще функция-фабрика с functools.cache, чем метаклассы.
Где спотыкаются
Считают подчёркивание или __all__ запретом импорта. Это подсказка для людей и линтеров, интерпретатор явный импорт пропустит.
Пишут синглтон через __new__ и забывают, что __init__ выполняется при каждом вызове _Singleton(). Состояние единственного объекта молча перезаписывается.
Не знают, что один файл, импортированный под двумя именами (app.config и config из-за разных путей в sys.path), даёт два объекта модуля и два «единственных» экземпляра. То же делает importlib.reload().