Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Введение + Singleton и Monostate
intro_singleton

Введение + Singleton и Monostate

Что такое паттерны проектирования и когда они нужны. Singleton через __new__, метаклассы и декораторы. Monostate (Borg) как Pythonic-альтернатива.

Введение в паттерны проектирования + Singleton и Monostate

Паттерн — это не решение, которое нужно применить. Это решение, которое нужно понять.


#Что такое паттерн проектирования?

Паттерн проектирования — это повторяемое решение распространённой проблемы проектирования. Это не готовый код, который можно скопировать и вставить, а шаблон, который адаптируется под конкретную задачу.

#Зачем нужны паттерны?

  1. Общий словарь — когда вы говорите «Observer», другой разработчик понимает, о чём речь
  2. Проверенные решения — паттерны отточены десятилетиями практики
  3. Избегание ошибок — многие антипаттерны уже известны

#Когда паттерны НЕ нужны?

# ❌ ПЛОХО: Преждевременное применение паттернов class ConfigFactorySingletonProxy: """Комбинация 3 паттернов там, где нужен просто модуль""" _instance = None def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance # ✅ ХОРОШО: Pythonic-решение # config.py — модуль уже singleton по природе database_url = "postgresql://localhost/mydb" debug = True

Правило: Используйте паттерн, когда проблема стала очевидной, а не когда вы читаете книгу по паттернам.


#Singleton: гарантия одного экземпляра

Singleton гарантирует, что у класса есть только один экземпляр, и предоставляет глобальную точку доступа к нему.

#Проблема, которую решает Singleton

# ❌ Проблема: несколько экземпляров там, где нужен один class DatabaseConnection: def __init__(self): self.connection = self._connect() def _connect(self): print("Создаю новое соединение с БД") # ... реальное подключение ... return "connection" # В разных частях кода создаются новые соединения db1 = DatabaseConnection() # Создаю новое соединение с БД db2 = DatabaseConnection() # Создаю новое соединение с БД # ❌ Два соединения вместо одного — расточительно!

#Реализация 1: Через new

class Singleton: _instance = None def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance class DatabaseConnection(Singleton): def __init__(self): # ⚠️ __init__ вызывается КАЖДЫЙ раз при создании! self.connection = self._connect() def _connect(self): print("Создаю соединение") return "connection" db1 = DatabaseConnection() # Создаю соединение db2 = DatabaseConnection() # Создаю соединение (снова!) print(db1 is db2) # True — экземпляр один # Но __init__ вызвался дважды! ⚠️

Проблема: __init__ вызывается при каждом обращении к конструктору, даже если экземпляр уже существует.

#Реализация 2: С блокировкой init

class Singleton: _instance = None _initialized = False def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance def __init__(self): if not self._initialized: self.connection = self._connect() self._initialized = True def _connect(self): print("Создаю соединение") return "connection" db1 = DatabaseConnection() # Создаю соединение db2 = DatabaseConnection() # Ничего не происходит print(db1 is db2) # True

#Реализация 3: Через метакласс (Pythonic)

class SingletonMeta(type): _instances = {} def __call__(cls, *args, **kwargs): if cls not in cls._instances: cls._instances[cls] = super().__call__(*args, **kwargs) return cls._instances[cls] class DatabaseConnection(metaclass=SingletonMeta): def __init__(self): self.connection = self._connect() def _connect(self): return "connection" db1 = DatabaseConnection() db2 = DatabaseConnection() print(db1 is db2) # True

Преимущество: __init__ вызывается только один раз, потому что __call__ не создаёт новый экземпляр.

#Реализация 4: Через декоратор (наиболее Pythonic)

from functools import wraps def singleton(cls): instances = {} @wraps(cls) def get_instance(*args, **kwargs): if cls not in instances: instances[cls] = cls(*args, **kwargs) return instances[cls] return get_instance @singleton class DatabaseConnection: def __init__(self): self.connection = self._connect() def _connect(self): return "connection" db1 = DatabaseConnection() db2 = DatabaseConnection() print(db1 is db2) # True

Важно: db1 и db2 — это не экземпляры DatabaseConnection, а результат вызова get_instance().

#Реализация 5: Модуль как Singleton (самый Pythonic)

# config.py database_url = "postgresql://localhost/mydb" debug = True # main.py import config print(config.database_url) # Доступ к состоянию модуля # other.py import config print(config.database_url) # Тот же объект! # ✅ Модуль в Python — singleton по природе # import всегда возвращает один и тот же объект из sys.modules

Вывод: В Python модуль — это встроенный singleton. Используйте модуль вместо класса-singleton, когда возможно.


#Monostate (Borg): состояние важнее экземпляра

Monostate (также известный как Borg) — это вариант singleton, где у класса может быть много экземпляров, но все они разделяют одно и то же состояние.

#Реализация через общий dict

class Borg: _shared_state = {} def __init__(self): self.__dict__ = self._shared_state class DatabaseConnection(Borg): def __init__(self): super().__init__() if not hasattr(self, 'connection'): self.connection = self._connect() print("Создаю соединение") def _connect(self): return "connection" db1 = DatabaseConnection() # Создаю соединение db2 = DatabaseConnection() # Ничего не происходит db3 = DatabaseConnection() # Ничего не происходит print(db1 is db2) # False — разные экземпляры! print(db1.connection is db2.connection) # True — общее состояние! db1.connection = "new_connection" print(db2.connection) # new_connection — изменение видно во всех!

#Сравнение Singleton vs Monostate

АспектSingletonMonostate
ЭкземплярыОдинМного
СостояниеОдноОбщее
НаследованиеСложноЛегко
ТестированиеСложно (глобал)Легче (можно сбросить)
PythonicНетБолее Pythonic

#Monostate для тестирования

class Config(Borg): _shared_state = {} def __init__(self): super().__init__() if not hasattr(self, 'debug'): self.debug = False self.database_url = "prod://db" # В тестах можно легко сбросить состояние def test_something(): Config._shared_state = {} # Сброс! config = Config() assert config.debug == False

#Когда Singleton — антипаттерн?

#Проблема 1: Глобальное состояние

# ❌ ПЛОХО: Singleton как глобальная переменная class Logger(Singleton): def log(self, msg): print(msg) # Везде в коде: Logger().log("message") # Скрытая зависимость!

Проблема: Невозможно подменить логгер в тестах, скрытая зависимость.

#Решение: Dependency Injection

# ✅ ХОРОШО: Явная зависимость class Service: def __init__(self, logger): self.logger = logger def do_work(self): self.logger.log("working") # В production: service = Service(Logger()) # В тестах: class MockLogger: def log(self, msg): pass service = Service(MockLogger()) # Легко тестировать!

#Проблема 2: Нарушение SRP

# ❌ ПЛОХО: Singleton делает слишком много class AppManager(Singleton): def __init__(self): self.db = Database() self.cache = Cache() self.logger = Logger() self.config = Config() # ... ещё 20 зависимостей ...

Решение: Разделите на отдельные компоненты с явными зависимостями.


#Практическое применение

#Кэш как Monostate

class Cache(Borg): _shared_state = {} def __init__(self): super().__init__() if not hasattr(self, '_data'): self._data = {} def get(self, key): return self._data.get(key) def set(self, key, value): self._data[key] = value # Использование cache1 = Cache() cache1.set("user:1", {"name": "Alice"}) cache2 = Cache() print(cache2.get("user:1")) # {"name": "Alice"} — общие данные!

#Логгер через модуль

# logger.py import logging logger = logging.getLogger("myapp") logger.setLevel(logging.INFO) handler = logging.StreamHandler() logger.addHandler(handler) # main.py from logger import logger logger.info("Application started")

#Что дальше?

Изучите тему Factory Method и Abstract Factory для создания объектов без привязки к конкретным классам.

Далее: Factory Method и Abstract Factory