Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Singleton (Одиночка)
Singleton — это порождающий паттерн проектирования, который гарантирует, что у класса есть только один экземпляр (инстанс), и предоставляет глобальную точку доступа к этому экземпляру. Это один из самых распространённых паттернов в программировании, используется для управления общими ресурсами или конфигурацией.
Основная идея
Singleton гарантирует, что во всём приложении существует только один объект класса, независимо от количества попыток создания новых экземпляров. Это полезно для:
- Управления ресурсами — подключение к БД, логирование
- Конфигурации — единая конфигурация приложения
- Кэширования — единый кэш для всего приложения
- Координации — единый диспетчер событий
Реализация Singleton в Python
1. Классический способ с методом класса
class Database:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self):
if self._initialized:
return
self.connection = None
self.port = 5432
self._initialized = True
print("Database initialized")
def connect(self):
if self.connection is None:
self.connection = f"Connected to DB on port {self.port}"
print(self.connection)
return self.connection
# Использование
db1 = Database()
db2 = Database()
print(db1 is db2) # True - один и тот же объект
db1.connect() # Connected to DB on port 5432
db2.connect() # Вернёт существующее подключение (не инициализирует заново)
2. Реализация с использованием декоратора
def singleton(cls):
instances = {}
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class Logger:
def __init__(self):
self.logs = []
print("Logger initialized")
def log(self, message: str):
self.logs.append(message)
print(f"[LOG] {message}")
def get_logs(self):
return self.logs
# Использование
logger1 = Logger()
logger2 = Logger()
logger1.log("First message")
logger2.log("Second message")
print(logger1 is logger2) # True
print(logger1.get_logs()) # ["First message", "Second message"]
3. Реализация с использованием метакласса
class SingletonMeta(type):
"""Метакласс для создания Singleton классов"""
_instances = {}
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 Configuration(metaclass=SingletonMeta):
def __init__(self):
self.settings = {}
print("Configuration initialized")
def set_value(self, key: str, value):
self.settings[key] = value
def get_value(self, key: str):
return self.settings.get(key)
# Использование
config1 = Configuration()
config2 = Configuration()
config1.set_value("debug_mode", True)
config2.set_value("timeout", 30)
print(config1 is config2) # True
print(config1.get_value("debug_mode")) # True
print(config2.get_value("timeout")) # 30
4. Thread-safe реализация
Для многопоточных приложений нужна синхронизация:
import threading
class ThreadSafeDatabase:
_instance = None
_lock = threading.Lock()
def __new__(cls):
if cls._instance is None:
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self):
if self._initialized:
return
with self._lock:
if not self._initialized:
self.connection = None
self._initialized = True
print("Database initialized")
def execute_query(self, query: str):
print(f"Executing: {query}")
# Использование в многопоточной среде
def create_connection():
db = ThreadSafeDatabase()
db.execute_query("SELECT * FROM users")
threads = [threading.Thread(target=create_connection) for _ in range(5)]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
5. Модульный Singleton (Pythonic способ)
В Python часто используется модульный уровень вместо класса:
# database.py
class _Database:
def __init__(self):
self.connection = None
print("Database module loaded")
def connect(self):
if self.connection is None:
self.connection = "Connected"
print("Database connected")
return self.connection
# Создаём единственный экземпляр на уровне модуля
database = _Database()
# main.py
from database import database
db1 = database # Получаем singleton
db2 = database # Тот же объект
print(db1 is db2) # True
db1.connect() # Database connected
Практические примеры
Система логирования
import threading
from datetime import datetime
class Logger:
_instance = None
_lock = threading.Lock()
def __new__(cls):
if cls._instance is None:
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self):
if self._initialized:
return
self._initialized = True
self.logs = []
def info(self, message: str):
log_entry = f"[{datetime.now().isoformat()}] INFO: {message}"
self.logs.append(log_entry)
print(log_entry)
def error(self, message: str):
log_entry = f"[{datetime.now().isoformat()}] ERROR: {message}"
self.logs.append(log_entry)
print(log_entry)
# Использование
logger = Logger()
logger.info("Application started")
logger.error("Connection failed")
Управление конфигурацией
from typing import Any, Optional
class Config:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self):
if self._initialized:
return
self._initialized = True
self.settings = {}
def load_from_dict(self, data: dict):
self.settings.update(data)
def get(self, key: str, default: Any = None) -> Any:
return self.settings.get(key, default)
def set(self, key: str, value: Any):
self.settings[key] = value
# Использование
config = Config()
config.load_from_dict({
"database_url": "postgresql://localhost/mydb",
"debug": True,
"secret_key": "my-secret-key"
})
db_url = config.get("database_url")
print(db_url) # postgresql://localhost/mydb
Недостатки и когда избегать Singleton
- Сложность тестирования — трудно мокировать и тестировать
- Скрытые зависимости — глобальное состояние затемняет зависимости
- Проблемы многопоточности — требует синхронизации
- Нарушение принципа SRP — класс отвечает и за логику, и за управление инстансом
Альтернативы
# Вместо Singleton лучше использовать внедрение зависимостей
class Service:
def __init__(self, logger):
self.logger = logger
def do_work(self):
self.logger.info("Working...")
# Или использовать фабрику
class LoggerFactory:
@staticmethod
def create_logger():
return Logger()
Итоги
Singleton — мощный паттерн для управления единственным экземпляром критичного ресурса. В Python его чаще всего реализуют через декораторы или метаклассы. Однако в современном коде часто предпочитают внедрение зависимостей вместо глобальных Singleton объектов.