🐍 3 лучших паттерна проектирования в Python: синглтон, декоратор и итератор
Kaggle expert⚛️ Пишу материал о различных алгоритмах и техниках в сфере Machine Learning. Паттерны в Python – это шаблоны для решения задач, которые часто встречаются в практике программиста. Они представляют из себя огромный набор инструментов. В этом материале вы познакомитесь с самыми главными из них. Благодаря книге «Паттерны проектирования: Elements of Reusable Object-Oriented Softwar» (авторы Эрих Гамма, Ричард Хелм, Ральф Джонсон и Джон Влиссидес), шаблоны (паттерны) приобрели популярность в компьютерной науке. В отрасли ее называют Gangs of Four – «Банда четырех». Эта книга учит мозг улавливать паттерны, которые в дальнейшем можно применить к существующему коду, чтобы повысить эффективность вашей разработки. Следует изучить как можно больше паттернов. Это позволит выбрать нужный, когда его использование будет необходимо. Большая часть книги посвящена паттернам для языка программирования Java и C++. Однако, в этой статье мы сделаем упор на использование паттернов в языке Python. Рассмотрим несколько шаблонов проектирования из каждой категории, согласно изначально предложенной классификации внутри книги, которые показались мне наиболее интересными в контексте программирования на Python. В разработке программного обеспечения паттерн – это общее, многократно используемое решение проблемы, которая часто встречается внутри конкретной ситуации. Это похоже на готовые чертежи, которые можно использовать для решения проблемы в вашем коде. Стоит отметить, что нельзя применять паттерн проектирования так же, как используется функция из импортированной библиотеки. Вместо этого, вы должны следовать концепции паттерна и реализовать решение, которое соответствует требованиям вашей программы. Паттерн – это не фрагмент кода, а общая концепция, которая описывает, как решить конкретную повторяющуюся проблему. Изначально существовало две основные классификации паттернов проектирования: Принимая во внимание первую классификацию, паттерны можно разделить на три группы: Позже появились новые паттерны проектирования, из которых можно выделить еще одну категорию: Concurrency (параллелизм) – это тот тип паттернов проектирования, который имеет дело с многопоточной парадигмой программирования. Синглтон (одиночка) – это паттерн проектирования, цель которого ограничить возможность создания объектов данного класса одним экземпляром. Он обеспечивает глобальность до одного экземпляра и глобальный доступ к созданному объекту. Примеры использования Пример кода: Первый наивный подход (naive approach): Что не так с этим кодом? Он нарушает принцип единственной ответственности и имеет нестандартный доступ к классу. Необходимо помнить, что доступ к экземплярам класса осуществляется только методом Итак, проблемы из предыдущего примера решены. Но возможно ли найти более оптимальный способ (без наследования классов)? Давайте попробуем. Все работает. Однако, надо сделать еще одну настройку – подготовить программу к работе в многопоточной среде. Вывод: Подведем итоги. Особенности использования Синглтона: Декоратор – это структурный паттерн. Цель которого – предоставление новых функциональных возможностей классам и объектам во время выполнения кода. Чаще всего декоратор представляет собой абстрактный класс, принимающий в конструкторе объект, функциональность которого мы хотим расширить. Но в Python есть и встроенный механизм декораторов, который можно использовать. Случаи использования Пример кода Используя декораторы, вы можете обернуть объекты несколько раз, поскольку и цель, и декораторы реализуют один и тот же интерфейс. Получаемый объект будет обладать объединенной и сложенной функциональностью всех декораторов. Вывод: Практичный пример с использованием встроенного механизма декораторов: Вывод: Без использования декоратора кэша для функции, которая рекурсивно вычисляет n-й член ряда Фибоначчи, трудно вычислить результат для значения 100 за все время работы. Подведем итоги. Возможности декоратора: При применении этого паттерна возникают следующие сложности: Итератор – это поведенческий паттерн. Его цель – позволить вам обходить элементы коллекции, не раскрывая ее базовое представление. Чтобы реализовать итератор в Python, у нас есть два возможных варианта: Примеры использования Пример кода Создание пользовательской коллекции с итератором алфавитного порядка: Вывод: Следующий пример относится к генератору, который представляет собой особый вид функции. Функция может быть приостановлена и возобновлена с того места, где была совершена пауза. На основе сохраненного состояния можно возвращать различные значения при последующих вызовах генератора. Вывод: Подведем итоги. Возможности итератора: Использование этого паттерна будет лишним, если ваше приложение работает только с простыми коллекциями. Более того, использование итератора может быть менее эффективным, чем прямой обход элементов какой-либо специализированной коллекции. Знание паттернов важно для современного разработчика. Оно помогает решить проблемы внутри вашего кода, используя принципы объектно-ориентированного программирования. Этот материал дает возможность познакомиться с основами работы с паттернами. *** 9 февраля стартует курс «Архитектуры и шаблоны проектирования», на котором вы научитесь: Для старта достаточно знать любой объектно-ориентированный язык программирования: Python, Java, PHP, C++, JavaScript, C# и др. Интересно, хочу попробовать Alex Maszański
Что такое паттерн проектирования?
Классификация паттернов проектирования
Паттерн 1: Синглтон
class Logger: @staticmethod def get_instance(): if '_instance' not in Logger.__dict__: Logger._instance = Logger() return Logger._instance def write_log(self, path): pass if __name__ == "__main__": s1 = Logger.get_instance() s2 = Logger.get_instance() assert s1 is s2
get_instance()
. Попытаемся исправить эти проблемы в следующем примере кода:
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()
FOO FOO
Паттерн 2: Декоратор
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({self.component.operation()})" class ConcreteDecoratorB(Decorator): def operation(self): return f"ConcreteDecoratorB({self.component.operation()})" 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 <= 1: return n else: return fib(n - 1) + fib(n - 2) if __name__ == "__main__": sys.setrecursionlimit(2000) print(fib(750))
2461757021582324272166248155313036893697139996697461509576233211000055607912198979704988704446425834042795269603588522245550271050495783935904220352228801000
Паттерн 3: Итератор
__iter__
и __next__
.
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]
Заключение
Хочу освоить больше паттернов, этому где-нибудь учат?
Что нужно для старта?
- 0 views
- 0 Comment
Свежие комментарии