Что такое паттерны в python
Перейти к содержимому

Что такое паттерны в python

  • автор:

Coming Soon!

This is going to be another great website hosted by PythonAnywhere.

PythonAnywhere lets you host, run, and code Python in the cloud. Our free plan gives you access to machines with everything already set up for you. You can develop and host your website or any other code directly from your browser without having to install software or manage your own server.

Need more power? Upgraded plans start at $5/month.

Developer info

Hi! If this is your PythonAnywhere-hosted site, then you’re almost there — you just need to create a web app to handle this domain.

Передовые паттерны проектирования в Python

Цель данной статьи — показать передовые паттерны в Python и лучший способ их использовать. В зависимости от того, что вам нужно от структуры кода, будь то быстрый поиск, постоянство, индексация и т.д., вы можете выбрать оптимальную структуру для работы, и большую часть времени вы будете смешивать их вместе что бы получить логичную и простую для понимания модель данных. Паттерны в Python очень интуитивны с точки зрения синтаксиса и они предлагают большой выбор операций. Это руководство пытается собрать наиболее распространенную и полезную информацию о каждом паттерне, а так же советы о том когда лучше всего использовать тот или иной паттерн.

Генераторы

Если вы очень долго используете Python, скорее всего вы слышали о генераторах списков. Они подходят для цикла, блока if и способны уместить все это в одну строку. Другими словами, вы можете отобразить (map) и отфильтровать (filter) список одним выражением.

Генератор списка состоит из следующих частей:

  • входная последовательность
  • переменная, представляющая элементы входной последовательности
  • условие (опционально)
  • выходное выражение, полученное из элементов входного списка, которые удовлетворяют условию

Допустим, нам нужно получить список всех квадратов целых чисел, которые больше нуля:

Согласитесь, довольно просто? Но это занимает 4-е строчки, 2-у уровня вложенности, и вдобавок мы делаем довольно тривиальную вещь. Вы можете уменьшить количество кода с помощью функций filter, lambda и map:

Теперь код расширяется горизонтально! Что можно сделать, что бы упростить код? Применить генераторы списков.

Генератор списков заключен в квадратные скобки, таким образом, видно что список производится сразу. В этом генераторе списка только один вызов функции и нет вызовов загадочной lambda — используется обычный итератор, выходное выражение и опциональное условие.

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

Генераторы выручат и сейчас. Выражение-генератор не загружает весь список в память сразу, а вместо этого создает объект генератора, поэтому за один раз можно получить только один элемент.

Выражения-генераторы имеют синтаксис, похожий на синтаксис генераторов списков, только вместо квадратных скобок — круглые:

Это даже немного эффективнее использования генераторов списков. Заменим пример более эффективным кодом:

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

Так же, вы можете использовать функцию zip для работы с двумя и более элементами за раз:

Пример двухуровневого генератора с использованием os.walk():

Декораторы

Декораторы предоставляют очень удобный метод для добавления функциональности для существующих функций и классов. Похоже на АОП (аспектно-ориентированное программирование) в Java, не так ли? Кроме того, что это проще, а значит мощнее. Например, предположим вам нужно что-либо сделать на точках входа и выхода их функции (защиту, отслеживание, блокирование и др. — стандартные элементы АОП).

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

Символ @ указывает на применение декоратора.

Теперь давайте реализуем код декоратора. Здесь мы фактически используем код декорируемой функции:

Когда вы пишете код вроде этого:

это то же самое, как если бы выполнялось следующее

Код внитри декоратора обычно включает в себя создание новой функции, которая принимает любые аргументы (с использованием *args и **kwargs) как показано в случае с функцией wrapper в этом примере. Внутри этой функции вы принимает входные аргументы оригинальной функции и возвращаете результат. Однако, вы так же можете разместить там дополнительный код (например, замер времени и т.д.). Таким образов созданная функция-обертка возвращает результат, как если бы это была оригинальная функция.

Давайте рассмотрим другой пример:

Когда компилятор проходит этот код, function() компилируется и получившийся объект-функция передается в код декоратора decorator , который создает из нее другой объект-функцию, что бы заменить первоначальную функцию function() .

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

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

Что должен делать декоратор после вызова? Вообще-то, все что угодно, но обычно ожидается, что будет использован код оригинальной функции. Однако, это не обязательно:

ContextLib (менеджеры контекста)

Модуль contextlib содержит средства для работы с менеджерами контекста и оператором with. Обычно, что бы написать менеджер контекста, вы определяете класс с методами __enter__() и __exit__() . Например:

Менеджер контекста “включается” оператором with. Возвращаемый объект будет использоваться в контексте. Метод __enter__() выполняется, когда поток управления входит в блок кода внутри оператора with. Когда поток управления покидает блок кода внутри with, вызывается метод __exit__() , что бы очистить используемые ресурсы.

Заново напишем исходный пример, используя декоратор @contextmanager из модуля contextlib:

В функции demo(label) весь код до оператора yield исполняется как метод менеджера контекста __enter__() . Весь код после оператора yield выполняется как метод __exit__() . Если в блоке внутри with возникнет исключение, оно “объявится” на месте оператора yield.

Дескрипторы

Дескрипторы определяют как осуществляется доступ к аттрибутам объекта. Дескриптор является способом изменить то, что происходит, когда вы обращаетесть к аттрибуту объекта.

Что бы создать дескриптор, нужно определить хотя бы один из следующих трех методов. Обратите внимание, что instance — это объект, к аттрибуту которого меняется доступ, а owner — класс, для которого дескриптор является аттрибутом.

__get__(self, instance, owner) — вызывается, когда запрашивается аттрибут (value = obj.attr); то что возвращается будет передано коду, запрашивающему аттрибут.

__set__(self, instance, value) — вызывается, когда аттрибуту устанавливается значение (obj.attr = value); ничего не возвращает.

__delete__(self, instance) — вызывается, когда аттрибут объекта удалаяется (del obj.attr)

Дескрипторы — это обобщение концепции связанных методов, лежащих в основе реализации классических классов. В классических классах, когда аттрибут экзампляра не найден в словаре экземпляра, поиск продолжается в словаре класса, а затем рекурсивно в словарях базовых классов. Когда аттрибут найден в словаре класса (не в словаре экземпляра), интерпретатор проверяет, является ли найденный объект функцией. Если это так, то возвращается не найденный объект, а обернутый объект, который действует как каррированая функция. Когда вызывается обернутый объект, он вызывает оригинальную функцию с экземпляром в качестве одного из аргументов.

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

Метаклассы

Метаклассы предлагают мощный способ изменить поведение классов в Python.

Метакласс определяется как “класса класса”. Любой класс, экземпляры которого являются сами классы, является метаклассом.

Мы создали класс и объект этого класса. Запросив у экземпляра аттрибут __class__ , мы увидели, что это demo . Дальше интереснее. Что такое класс demo ? У него мы тоже можем посмотреть аттрибут __class__ — это type .

Итак, type — это класс классов в Python. Другими словами, в приведенном выше примере obj — это объект класса demo , сам класс demo является объектом type .

Таким образом это делает type метаклассом — в действительности наиболее часто используемый метакласс в Python, т.к. это дефолтный метакласс для всех классов.

Т.к. метакласс — это класс классов, он используется для создания классов (тех, что создают объекты). Но подождите, не мы ли создаем классы, когда определяем их стандартным способом? Все верно, но то что делает Python “под капотом” выглядит так:

  • когда встречается определение класса, Python собирает аттрибуты (включая методы) в словарь
  • когда определение класса закончилось, Python определяет для него метакласс; давайте назовем его Meta
  • после Python выполняет Meta(name, bases, dct) , где:
    • Meta — это метакласс, поэтому этот вызов создает его экземпляр
    • name — это имя только что созданного класса
    • bases — это кортеж базовых классов
    • dct — словарь, связывающий названия аттрибутов с объектами; в нем перечислены все аттрибуты класса

    Как определить какой метакласс у класса? Если у класса (или один из его базовых классов) имеет аттрибут __metaclass__ , то он считается метаклассом. В противном случае, метаклассом является type.

    Паттерны

    “Проще просить прощения, чем разрешения”

    Один из принципов Python — “Проще просить прощения, чем разрешения”. В отличие от подхода “семь раз отмерь”, этот принцип заключается в том, что сначала вы должны попытаться выполнить действие и если возникает ошибка — реагировать соответствующим образом. Продвинутая обработка исключений в Python поддерживает этот принцип и помогает разрабатывать надежные и устойчивые программы.

    Синглтон (одиночка)

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

    Null object

    Null object может быть использован вместо None, что бы избежать проверки на None.

    Обозреватель

    Паттерн обозреватель позволяет нескольким объектам иметь доступ к общим данным.

    Конструктор

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

    Заключение

    Спасибо за чтение. Оставляйте свои ​​комментарии для дальнейшего обсуждения.

    Name already in use

    Work fast with our official CLI. Learn more about the CLI.

    Sign In Required

    Please sign in to use Codespaces.

    Launching GitHub Desktop

    If nothing happens, download GitHub Desktop and try again.

    Launching GitHub Desktop

    If nothing happens, download GitHub Desktop and try again.

    Launching Xcode

    If nothing happens, download Xcode and try again.

    Launching Visual Studio Code

    Your codespace will open once ready.

    There was a problem preparing your codespace, please try again.

    Latest commit

    Git stats

    Files

    Failed to load latest commit information.

    README.md

    Паттерны в python (Patterns)

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

    Порождающие паттерны(Creational Patterns):

    беспокоятся о гибком создании объектов без внесения в программу лишних зависимостей.

    Design patterns in Python

    Lisa Plitnichenko

    Their main goal is to show us good ways to program things and explain why other options won’t work.

    Using common design patterns, you can:

    • Speed up the development process;
    • Reduce the number of lines of code;
    • Make sure your code is well-designed;
    • Anticipate future problems arising from small issues.

    Design patterns can considerably improve the life of a software developer disregarding the programming language (s)he uses.

    I’ve interviewed the Founder & CTO of Jellyfish.tech, Python developer & software architect with 9+ years of experience Roman Latyshenko on his top design patterns.

    I mainly work with Python/ Django, so here is my list of top patterns in Python I use daily in my job.

    Behavioral patterns

    Iterator

    Iterator allows traversing the elements of collections without exposing the internal details.

    Use case. Mostly, I use it to provide a standard way of traversing the collections.

    ➕ Clean client code (Single Responsibility Principle).

    ➕ Introducing iterators in collections is possible without changing the client’s code (Open/Closed Principle).

    ➕ Each iteration object has its own iteration state, so you can delay & continue iteration.

    ➖ Use of iterators with simple collections can overload the application.

    Structure

    Code example

    State

    State helps an object to alter its behavior in case its internal state changes.

    Use case. State helps me

    • Alter the enormous number of object states.
    • Reduce the number of lines with duplicate code in similar transitions & states.
    • Avoid massive conditionals.

    ➕ Follows Single Responsibility principle: separate classes for the code related to a different state.

    ➕ Doesn’t change the context or state of classes when adding new states (Open/Closed Principle).

    ➖ Using State can be too much in case the state machine isn’t almost changing.

    Structure

    Code example

    Observer

    Observer notifies about events happening in other objects they observe without coupling to their classes.

    Use case. Each time I need to add the subscription mechanism to let an object subscribe to/ unsubscribe from notifications on the events happening with a specific publisher class, I use the Observer pattern.

    A good example is a simple subscription to news from any online magazine, frequently with the option to choose your sphere of interest (science, digital technology, etc.). Alternatively, the button “Notify me when it’s in stock” for e-commerce platforms is another example.

    ➕ You haven’t to change the publisher’s code to add subscribers’ classes.

    ➖ Subscribers get notifications in random order.

    Structure

    Code example

    Structural patterns

    Facade

    Facade provides a simplified yet limited interface to decrease the complexity of an application. Complex subsystems with multiple moving parts could be “masked” by Facade.

    Use case. I create the Facade class in case I have to work with complex libraries & APIs and/ or I need only the part of their functionality.

    ➕ System complexity is separated from the code

    ➖ Using the Facade pattern, you can create a god object.

    Structure

    Code example

    Decorator

    Decorator attaches new behaviors to the objects without modifying their structure.

    The pattern produces a decorator class to wrap the original one and add new functionality.

    Use case. I use the Decorator pattern each time I need to add extra behaviors to objects without getting into the code.

    ➕ Changes the object behavior without creating a subclass.

    ➕ You can combine several behaviors by wrapping an object into multiple decorators.

    ➖ A specific decorator is hard to remove from the wrappers stack.

    Structure

    Code example

    Adapter

    Adapter serves as the middle-layer class to join functionalities of either independent or incompatible interfaces.

    Use case. Setting up the collaboration between the interfaces, I use the Adapter pattern to resolve the problem of incompatible formats.

    For example, Adapter could help convert XML data format to JSON for further analysis.

    ➕ Allows separating the interface from business logic.

    ➕ Adding new adapters doesn’t break the client’s code

    ➖ Increases the code complexity

    Structure

    Code example

    Creational patterns

    Singleton

    Singleton restricts a class from having more than one instance and ensures a global access point to this instance.

    Use case. Singleton helps me

    • Manage a shared resource: i.e. a single database, file manager, or printer spooler shared by multiple parts of the application.
    • Store a global state (help filepath, user language, application path, etc.).
    • Create a simple logger.

    ➕ Class has a single instance

    ➖ It’s hard to unit test the code as the majority of test frameworks use inheritance when creating mock objects.

    Structure

    Code example

    When to use design patterns for Python?

    Facade is good when you need a unified interface for several API options. I.e. you should integrate a payment system in the application, leaving the possibility to change it. In this case, you could use the Facade pattern, and you’ll only have to create a new facade without rewriting the whole application.

    The problem here can emerge if only APIs are quite different, as it isn’t an easy task to design the common interface for facades.

    State is used to manage the multiple independent components of an application provided that the initial architecture implies their independence. So, creating a separate module for state management can be a good idea, as well as using the Observer pattern.

    Decorator is probably the most used Python pattern, because of in-built decorator support. For example, Decorator provides a convenient and explicit way to use some libraries and creates ever-richer opportunities for application design & management. The pattern also ensures a wide possibility for function composition and discovers new opportunities in functional programming.

    Adapter is a fit when working with a big amount of data in different formats. The pattern allows using one algorithm instead of multiple ones for every data format.

    The similar benefits Iterator has, so they are okay to be used together. In addition, one of the Iterator variations called Generator (introduced in Python long ago) allows using memory more efficiently working with big amounts of data that is highly valuable for some types of projects.

    Finally, the importance of Singleton can’t be underestimated: database connection, working with API, working with files… These all are the moments when a developer should have a clear idea of how the process goes to avoid making mistakes. And Singleton can do a good job here, not to mention the possibility to reduce the memory consumption by using the same instance each time instead of duplicating it.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *