Python singleton что такое

Python singleton что такое

Он нарушает причины изменения, которые приняты в концепции SRP (single responsibility principle). Необходимо помнить, что доступ к экземплярам класса осуществляется только методом get_instance() . Проблема в дескрипторе, куда пишутся логи со стороны класса Logger .

class Singleton: _instances = <> def __new__(cls, *args, **kwargs): if cls not in cls._instances: instance = super().__new__(cls) cls._instances[cls] = instance return cls._instances[cls] class Logger(Singleton): def write_log(self, path): pass if __name__ == "__main__": logger1 = Logger() logger2 = Logger() assert logger1 is logger2 

Итак, проблемы из предыдущего примера решены. Но возможно ли найти более оптимальный способ (без наследования классов)?

class Singleton(type): _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 Logger(metaclass=Singleton): def write_log(self, path): pass if __name__ == "__main__": logger1 = Logger() logger2 = Logger() assert logger1 is logger2 

Все работает. Однако, надо сделать еще одну настройку – подготовить программу к работе в многопоточной среде.

from threading import Lock, Thread class Singleton(type): _instances = <> _lock: Lock = Lock() def __call__(cls, *args, **kwargs): with cls._lock: if cls not in cls._instances: instance = super().__call__(*args, **kwargs) cls._instances[cls] = instance return cls._instances[cls] class Logger(metaclass=Singleton): def __init__(self, name): self.name = name def write_log(self, path): pass def test_logger(name): logger = Logger(name) print(logger.name) if __name__ == "__main__": process1 = Thread(target=test_logger, args=("FOO",)) process2 = Thread(target=test_logger, args=("BAR",)) process1.start() process2.start() 

Подведем итоги. Особенности использования Синглтона:

  • Класс имеет только один экземпляр;
  • Вы получаете глобальную точку доступа к этому экземпляру;
  • Синглтон инициализируется только при первом запросе;
  • Маскирует плохой дизайн до определенного момента. Это одна из причин, почему многие считают синглтон антипаттерном.

Паттерн 2: Декоратор

Декоратор – это структурный паттерн. Цель которого – предоставление новых функциональных возможностей классам и объектам во время выполнения кода.

Чаще всего декоратор представляет собой абстрактный класс, принимающий в конструкторе объект, функциональность которого мы хотим расширить. Но в Python есть и встроенный механизм декораторов, который можно использовать.

  • Необходимость назначить дополнительные обязанности объектам во время выполнения, не ломая код, который использует эти объекты;
  • По каким-то причинам невозможно расширить «цепочку обязанностей» объекта через наследование.
Читайте также:  Анкета

Используя декораторы, вы можете обернуть объекты несколько раз, поскольку и цель, и декораторы реализуют один и тот же интерфейс.

Получаемый объект будет обладать объединенной и сложенной функциональностью всех декораторов.

from abc import ABC, abstractmethod class Component(ABC): @abstractmethod def operation(self): pass class ConcreteComponent(Component): def operation(self): return "ConcreteComponent" class Decorator(Component): def __init__(self, component): self.component = component @abstractmethod def operation(self): pass class ConcreteDecoratorA(Decorator): def operation(self): return f"ConcreteDecoratorA()" class ConcreteDecoratorB(Decorator): def operation(self): return f"ConcreteDecoratorB()" if __name__ == "__main__": concreteComponent = ConcreteComponent() print(concreteComponent.operation()) decoratorA = ConcreteDecoratorA(concreteComponent) decoratorB = ConcreteDecoratorB(decoratorA) print(decoratorB.operation()) 
ConcreteComponent ConcreteDecoratorB(ConcreteDecoratorA(ConcreteComponent)) 

Практичный пример с использованием встроенного механизма декораторов:

import sys def memoize(f): cache = dict() def wrapper(x): if x not in cache: cache[x] = f(x) return cache[x] return wrapper @memoize def fib(n): if n 
2461757021582324272166248155313036893697139996697461509576233211000055607912198979704988704446425834042795269603588522245550271050495783935904220352228801000 

Без использования декоратора кэша для функции, которая рекурсивно вычисляет n-й член ряда Фибоначчи, трудно вычислить результат для значения 100 за все время работы.

Подведем итоги. Возможности декоратора:

  • Расширение поведения объекта без создания подкласса;
  • Добавление или удаление обязанности объекта во время выполнения;
  • Объединение нескольких моделей поведения, путем применения к объекту нескольких декораторов;
  • Разделение монолитного класса, который реализует множество вариантов поведения на более мелкие классы;

При применении этого паттерна возникают следующие сложности:

  • Применение одной конкретной обертки (wrapper) из центра стека (stack);
  • Реализация декоратора, при исключении его зависимости от порядка, в котором обертки уложены в стек.

Паттерн 3: Итератор

Итератор – это поведенческий паттерн. Его цель – позволить вам обходить элементы коллекции, не раскрывая ее базовое представление.

Чтобы реализовать итератор в Python, у нас есть два возможных варианта:

Примеры использования

  • Коллекция имеет сложную структуру. Необходимо скрыть ее от клиента из соображений удобства или безопасности;
  • Требуется сократить дублирование обходного кода по всему приложению;
  • Обход элементов различных структур данных;
  • Изначально неизвестны детали структуры данных.

Создание пользовательской коллекции с итератором алфавитного порядка:

from collections.abc import Iterator, Iterable class AlphabeticalOrderIterator(Iterator): _position: int = None _reverse: bool = False def __init__(self, collection, reverse=False): self._collection = sorted(collection) self._reverse = reverse self._position = -1 if reverse else 0 def __next__(self): try: value = self._collection[self._position] self._position += -1 if self._reverse else 1 except IndexError: raise StopIteration() return value class WordsCollection(Iterable): def __init__(self, collection): self._collection = collection def __iter__(self): return AlphabeticalOrderIterator(self._collection) def get_reverse_iterator(self): return AlphabeticalOrderIterator(self._collection, True) if __name__ == "__main__": wordsCollection = WordsCollection(["Third", "First", "Second"]) print(list(wordsCollection)) print(list(wordsCollection.get_reverse_iterator())) 
['First', 'Second', 'Third'] ['Third', 'Second', 'First'] 

Следующий пример относится к генератору, который представляет собой особый вид функции. Функция может быть приостановлена и возобновлена с того места, где была совершена пауза. На основе сохраненного состояния можно возвращать различные значения при последующих вызовах генератора.

def prime_generator(): yield 2 primes = [2] to_check = 3 while True: sqrt = to_check ** 0.5 is_prime = True for prime in primes: if prime > sqrt: break if to_check % prime == 0: is_prime = False break if is_prime: primes.append(to_check) yield to_check to_check += 2 generator = prime_generator() print([next(generator) for _ in range(20)]) 
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71] 

Подведем итоги. Возможности итератора:

  • Очистить клиентский код и коллекции, вынеся код обхода в отдельные классы;
  • Реализовать новые типы коллекций и итераторов с передачей их в существующий код без нарушений;
  • Обходить одну и ту же коллекцию с помощью нескольких итераторов параллельно, учитывая, что каждый из них хранит информацию о состоянии итерации;
  • Возможность отложить итерацию и продолжить ее по мере необходимости;

Использование этого паттерна будет лишним, если ваше приложение работает только с простыми коллекциями. Более того, использование итератора может быть менее эффективным, чем прямой обход элементов какой-либо специализированной коллекции.

Заключение

Знание паттернов важно для современного разработчика. Оно помогает решить проблемы внутри вашего кода, используя принципы объектно-ориентированного программирования. Этот материал дает возможность познакомиться с основами работы с паттернами.

Хочу освоить больше паттернов, этому где-нибудь учат?

9 февраля стартует курс «Архитектуры и шаблоны проектирования» , на котором вы научитесь:

  • строить архитектуры приложений, которые позволяют не снижать скорость разработки по мере развития проекта;
  • писать модульные тесты на Mock-объектах;
  • применять SOLID принципы не только в объектно-ориентированных языках;
  • использовать CI и IoC контейнеры.

Что нужно для старта?

Для старта достаточно знать любой объектно-ориентированный язык программирования: Python, Java, PHP, C++, JavaScript, C# и др.

Источники

Источник

Создание Singleton в Python

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

Пример проблемы

Рассмотрим пример, когда у нас есть класс Logger, который записывает логи в определенный файл. Каждый раз, когда мы создаем новый экземпляр этого класса, он открывает файл и начинает запись с начала. Если у нас есть несколько экземпляров класса Logger, они будут перезаписывать лог-файл, и результаты будут непредсказуемыми. В этом случае было бы намного проще и безопаснее, если бы был только один экземпляр класса Logger, который бы отвечал за запись в лог-файл.

Решение проблемы

Для решения этой проблемы можно использовать паттерн Singleton. В Python есть несколько способов реализации этого паттерна:

Метод 1: Декоратор

Декоратор — это функция, которая принимает другую функцию и расширяет ее поведение, не изменяя ее явно. При использовании декоратора для реализации Singleton, мы создаем декоратор, который проверяет, был ли уже создан экземпляр класса, и если да, то возвращает этот экземпляр вместо создания нового.

def singleton(class_): instances = <> def getinstance(*args, **kwargs): if class_ not in instances: instances[class_] = class_(*args, **kwargs) return instances[class_] return getinstance @singleton class Logger: pass

Метод 2: Базовый класс

В этом методе мы создаем базовый класс Singleton, который содержит логику для проверки, был ли уже создан экземпляр класса. Затем мы создаем наш класс Logger, который наследуется от класса Singleton.

class Singleton(object): _instance = None def __new__(class_, *args, **kwargs): if not isinstance(class_._instance, class_): class_._instance = object.__new__(class_, *args, **kwargs) return class_._instance class Logger(Singleton): pass

Метод 3: Метакласс

Метаклассы в Python — это «классы классов», которые позволяют нам контролировать поведение классов таким же образом, как классы контролируют поведение своих объектов.

class Singleton(type): _instances = <> def __call__(cls, *args, **kwargs): if cls not in cls._instances: cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) return cls._instances[cls] class Logger(metaclass=Singleton): pass

Метод 4: Модуль

В Python каждый модуль загружается только один раз, поэтому мы можем использовать модуль как Singleton.

# logger.py class _Logger: pass logger = _Logger()

Заключение

В зависимости от конкретных потребностей и условий, каждый из этих методов может быть наилучшим выбором для реализации паттерна Singleton в Python.

Источник

Оцените статью