Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Паттерн Singleton
Singleton — это порождающий паттерн проектирования, который гарантирует, что класс имеет только один экземпляр (инстанс), и предоставляет глобальную точку доступа к этому экземпляру.
Когда использовать Singleton
✓ Логирование
✓ Конфигурация приложения
✓ Пул подключений к БД
✓ Кэш приложения
✓ Очередь событий
✗ Не используй просто так, если нет необходимости в одном экземпляре
✗ Singleton может усложнить тестирование (зависимость от глобального состояния)
Реализация 1: Использование new
Это классический способ, когда мы контролируем создание объекта:
class Logger:
_instance = None
def __new__(cls):
# __new__ вызывается перед __init__
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self):
# __init__ может быть вызван несколько раз
# поэтому проверяем флаг инициализации
if self._initialized:
return
self.logs = []
self._initialized = True
def add_log(self, message):
self.logs.append(message)
def get_logs(self):
return self.logs
# Использование
logger1 = Logger()
logger2 = Logger()
logger3 = Logger()
logger1.add_log("Event 1")
logger2.add_log("Event 2")
logger3.add_log("Event 3")
# Все ссылаются на один объект
print(logger1 is logger2) # True
print(logger1 is logger3) # True
print(logger1.get_logs()) # ["Event 1", "Event 2", "Event 3"]
# Попытка создать "новый" экземпляр все равно вернёт тот же
logger4 = Logger()
print(logger4 is logger1) # True
Реализация 2: Декоратор (рекомендуется)
Это более Pythonic способ и более гибкий:
def singleton(cls):
"""Декоратор для преобразования класса в Singleton"""
instances = {}
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class Database:
def __init__(self, host="localhost", port=5432):
self.host = host
self.port = port
self.connected = False
def connect(self):
self.connected = True
print(f"Подключено к {self.host}:{self.port}")
def execute(self, query):
if not self.connected:
self.connect()
return f"Выполнен запрос: {query}"
# Использование
db1 = Database()
db2 = Database()
db3 = Database()
print(db1 is db2) # True
print(db1 is db3) # True
db1.connect()
print(db2.execute("SELECT * FROM users")) # Использует уже подключённую БД
Реализация 3: Метакласс (продвинутый способ)
Метаклассы позволяют контролировать создание самого класса:
class SingletonMeta(type):
"""
Метакласс для создания Singleton классов.
__call__ вызывается при создании инстанса.
"""
_instances = {}
_lock = {} # Для потокобезопасности
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
# Создаём новый инстанс
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]
class Config(metaclass=SingletonMeta):
def __init__(self, filename="config.ini"):
self.filename = filename
self.settings = {}
def load(self, key):
# Симуляция загрузки конфигурации
self.settings[key] = f"Value of {key}"
def get(self, key):
return self.settings.get(key)
# Использование
config1 = Config()
config2 = Config("other.ini") # filename игнорируется, используется существующий инстанс
print(config1 is config2) # True
config1.load("database_url")
print(config2.get("database_url")) # "Value of database_url"
Реализация 4: Модульный уровень (самый простой)
Просто используй модуль как Singleton:
# logger.py
class _Logger:
def __init__(self):
self.logs = []
def add_log(self, message):
self.logs.append(message)
def get_logs(self):
return self.logs
# Создаём единственный экземпляр на уровне модуля
logger = _Logger()
# В других файлах просто импортируем
# from logger import logger
# logger.add_log("Event")
Потокобезопасный Singleton
Для многопоточных приложений нужна синхронизация:
import threading
class ThreadSafeSingleton:
_instance = None
_lock = threading.Lock() # Мьютекс для синхронизации
def __new__(cls):
# Double-checked locking паттерн
if cls._instance is None:
with cls._lock:
# Проверяем ещё раз после получения блокировки
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self):
if not hasattr(self, 'initialized'):
self.initialized = True
self.data = []
def add_data(self, item):
self.data.append(item)
def get_data(self):
return self.data
# Потокобезопасное использование
def worker():
singleton = ThreadSafeSingleton()
for i in range(100):
singleton.add_data(f"Item {i}")
threads = [threading.Thread(target=worker) for _ in range(5)]
for t in threads:
t.start()
for t in threads:
t.join()
singleton = ThreadSafeSingleton()
print(f"Всего элементов: {len(singleton.get_data())}") # 500
Проблемы Singleton и их решение
# Проблема 1: Сложность тестирования
class BadLogger:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def log(self, msg):
print(msg) # Сложно мокировать
# Решение: Dependency Injection
class GoodLogger:
def log(self, msg):
print(msg)
class Service:
def __init__(self, logger=None):
self.logger = logger or GoodLogger() # Инъекция
def do_work(self):
self.logger.log("Working...")
# В тестах можем передать mock
class MockLogger:
def __init__(self):
self.messages = []
def log(self, msg):
self.messages.append(msg)
test_logger = MockLogger()
service = Service(test_logger)
service.do_work()
assert test_logger.messages == ["Working..."]
Проблема 2: Сокрытие зависимостей
# Плохо: Singleton скрывает зависимость
class BadDatabaseQuery:
def execute(self):
db = DatabaseSingleton.get_instance()
return db.query() # Скрытая зависимость!
# Хорошо: Явная зависимость
class GoodDatabaseQuery:
def __init__(self, db):
self.db = db # Явная зависимость
def execute(self):
return self.db.query() # Ясно, откуда берётся БД
Когда НЕ использовать Singleton
# Неправильно: Singleton для всего подряд
class UserSingleton:
"""Попытка создать Singleton для пользователя (плохо!)"""
_instance = None
def __new__(cls, user_id):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance.user_id = user_id
return cls._instance
u1 = UserSingleton(1)
u2 = UserSingleton(2)
print(u1.user_id) # 1, не 2!
# Правильно: Просто создавай несколько объектов
class User:
def __init__(self, user_id):
self.user_id = user_id
u1 = User(1)
u2 = User(2)
print(u1.user_id) # 1
print(u2.user_id) # 2
Резюме
- new: Классический способ, работает с наследованием
- Декоратор: Pythonic, простой и гибкий (рекомендуется)
- Метакласс: Продвинутый способ, полный контроль
- Модульный уровень: Самый простой для Python
- Потокобезопасность: Используй блокировки или декораторы
- Альтернатива: Часто можно обойтись без Singleton через Dependency Injection
- Когда использовать: Логирование, конфигурация, пул подключений, кэш