Holy Python
497 subscribers
28 links
Станьте "папой" питона!
Download Telegram
#тайпхинты

Введение

Немного отвлечёмся от ООП и рассмотрим очень важную тему в питоне - тайпхинты.

Типизация

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

Тайпхинты

На помощь приходят, тайпхинты! Python сохранил идею динамической, утиной типизации и с версии Python 3.6 начал поддерживать тайпхинты.

Тайпхинт, по своей сущности похож на комментарий, ведь интерпретатор никак не учитывает его. Кроме того, что тайпхинты помогают читать код, также их учитывают популярные IDE и линтеры.

Синтаксис тайпхинтов

Питон поддерживает тайпхинты для: аргументов функции, возвращаемых значений функции и атрибутов класса.

Подсказка типа для аргумента, переменной и атрибута имеет такой синтаксис:


название: тип


Примеры:

test: int = 4

self.test: int = 4

def x(test: int):



Подсказка типа
для возвращаемого значения функции имеет такой синтаксис:



def название(...) -> тип:


Пример:


def test(...) -> int:

Дополнительные материалы

https://habr.com/ru/company/lamoda/blog/432656/
https://www.python.org/dev/peps/pep-3107/
https://www.python.org/dev/peps/pep-0526/
👍2
#тайпхинты

Введение

Продолжая тему тайпхинтов. Сегодня мы рассмотрим пакет typing, который добавляет много полезного функционала для тайпхинтов.

List, Tuple и Dict

Вы конечно можете использовать встроенные в питон типы list и tuple, однако, данные тайпхинты из пакета typing позволяют уточнить какие элементы хранятся в структуре.

Примеры:

List[int]
Tuple[int]
Dict[int, int]

Any

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

Union

Для случаев, если необходимо допустить использование ограниченного количества типов, есть тайпхинт Union. Чтобы его использовать нужно указать в квадратных скобках допустимый список типов.

Пример:

Union[int, str]

UPD: в питоне 3.10 Union стал ненужным, так как появилась такая конструкция: int | str, которая делает тоже самое, что и этот тайпхинт!

Optional

Optional - это тоже самое, что и Union[..., None] или ... | None.

С помощью Optional вы можете указать, что вы хотите, чтобы использовался не только указаный тип, но и None.

Пример:

Optional[int] # int | None

NoReturn

NoReturn нужен, когда функция никогда не возвращает управление.

Примеры:

def test() -> NoReturn:
sys.exit()


def test() -> NoReturn:
while True:
pass


Iterable

Если функция является генераторной и содержит yield, нужно использовать тайпхинт Iterable.

Пример:

def test() -> Iterable[int]:
yield 3
yield 4


Дополнительные материалы

https://docs.python.org/3/library/typing.html
🔥5👍1
#py2exe #pyinstsller #ошибки

Введение

Сегодня, я хотел бы разобрать такую библиотеку как pyinstaller, которая позволяет собирать python в exe.

Вопрос: "Почему я решил сделать этот пост?"
Ответ: "В последнее время, я заметил тенденцию того, что новички начали очень часто использовать pyinstaller для сборки exe, хотя данный инструмент несёт в себе только вред."

Процесс сборки exe

Как pyinstaller собирает exe(что-то похожее на него):,

Шаг 1: Считывает код файла
Шаг 2: Анализирует код, для получение всех зависимостей необходимых для работы
Шаг 3: Создаёт .spec файл конфигурации
Шаг 4: Собирает все файлы и добавляет к ним интерпретатор питона.
Шаг 5: Создаёт папку BUILD, куда записывает логи и рабочие файлы
Шаг 6: Создаёт папку DIST
Шаг 7: Записывает все необходимые файлы в одну папку или что-то похожее на exe.

Почему же он вреден?

1. Вес exe огромен
2. Это подобее exe пытается запустить то, что считало из себя, что может напоминать поведение троянов и как следствие вызывать подозрения у антивирусов
3. Если вы использовали флаг --onefile, то exe начинает очень сильно засорять парку tmp
4. Код никак не защищается(как думают многие не опытные программисты, для обфускации кода используйте pyarmor), даже если вы собирали pyc, а не py.
5. Огромное количество библиотек не работают в exe собранном данной библиотекой.
6. При сборке под линукс возникают проблемы с работой стандартной библиотеки.

Выводы

Pyinstaller - очень похож на обычный архиватор, который просто засовывает в исполняемый файл интерпретатор питона. Это самый плохой способ распространения программ на питоне.
👍12
#SOLID

Введение

Сегодня мы начнём очень важную и сложную тему в программировании - SOLID.

Что это такое и зачем это нужно?

SOLID - это 5 основных принципов ООП, предложенных Робертом Мартином, при соблюдении которых вы сможете без труда поддерживать и расширять вашу программу в течении долгого времени.

Расшифровка SOLID

S - single responsibility(принцип единой ответственности)
O - open-closed(принцип открытости закрытости)
L - Liskov substitution(принцип подстановки Барбары Лисков)
I - interface segregation(принцип разделения интерфейса)
D - dependency inversion(принцип инверсии зависимостей)

Дополнительные материалы

https://www.amazon.com/software-development-principles-razrabotka-printsipy/dp/5845905583
https://bit.ly/3NQQhs0
https://bit.ly/3auZM1D
👍5
#SOLID

Введение

Сегодня мы рассмотрим первый принцип SOLID - принцип единой ответственности.

Принцип единой ответственности

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

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

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

Дополнительные материалы

https://bit.ly/3Q7ywqu
https://web-creator.ru/articles/solid_the_single_responsibility_principle
👍2
#SOLID #задача

Введение

Продолжая тему SOLID. Сегодня мы рассмотрим второй принцип SOLID - принцип открытости/закрытости.

Принцип открытости/закрытости

Принцип открытости/закрытости гласит: "Программные сущности должны быть открыты для расширения, но закрыты для модификации."

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

Задача

Пусть у нас есть класс Discount, который позволяет выдавать скидки в вашем интернет магазине:

class Discount:
def __init__(self, customer: str, price: int | float):
self.customer = customer
self.price = price

def give_discount(self) -> int | float:
if self.customer == 'fav':
return self.price * 0.2

После некоторого времени работы вашего магазина, вы создали VIP карту, которую получили постоянные клиенты, данная карта увеличивает скидку на 20%, а значит нужно создать ещё одно условие, для проверки имеет ли пользователь вип карту. А что будет когда у вас появится ещё больше разновидностей скидок? Правильно, вам придётся писать всё новые и новые условия.
Этот код - пример нарушения принципа открытости/закрытости. Попробуйте исправить данную программу.
👍1
#SOLID #задача

Введение

Продолжая тему SOLID. Сегодня мы рассмотрим третий принцип SOLID - принцип подстановки Барбары Лисков

Принцип подстановки Барбары Лисков

Барбара Лисков в 1987 году сформулировала данный принцип так:

Пусть q(x) является свойством, верным относительно объектов x некоторого типа T. Тогда q(y) также должно быть верным для объектов y типа S, где S является подтипом типа T.

Звучит очень сложно, поэтому Роберт Мартин определил этот принцип так:

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

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

Пример

Пусть у нас есть класс Vehicle, в котором мы создали метод start_engine, в начале всё было замечательно мы создавали машины и мотоциклы, однако нам сказали сделать велосипед и в этот момент мы поняли, что нарушали LSP, ведь у велосипеда нет двигателя!

class Vehicle:
def start_engine(self):
pass

class Car(Vehicle):
def start_engine(self):
pass

class Bicycle(Vehicle):
def start_engine(self):
pass


Попробуйте исправить данную программу и прислать решение в комментарии.

Дополнительные материалы

https://www.sicpers.info/2018/03/why-inheritance-never-made-any-sense/
http://stephane.ducasse.free.fr/FreeBooks/BlueBook/Bluebook.pdf
👍62
#SOLID

Введение

Продолжая тему SOLID. Сегодня мы рассмотрим четвёртый принцип SOLID - принцип разделения интерфейса.

Принцип разделения интерфейса

Роберт Мартин определил этот принцип так:

Программные сущности не должны зависеть от методов, которые они не используют.

Иногда кажется хорошей идеей объявить один большой абстрактный класс или интерфейс, а затем дать клиентам его реализовать.
Однако данный подход в корне не верен. Представим, что вам дали задание создать темплейт тг бота. Вы создаёте три абстрактных метода: send, start, save_data.
Когда клиент реализует все абстрактные методы, то всё нормально, но если клиенту не нужен метод сохранения в БД? Что ему делать? Здесь есть два варианты развития событий:

1. Поставить затычку.
2. Начать возмущаться - вызвать ошибку и добавить докстринг.

Рассмотрим первый кейс. Клиент поставил затычку, вроде бы всё хорошо, однако данный метод будет виден для пользователей ботов => пользователи будут использовать его и не понимать, в чём проблема.

Рассмотрим второй кейс. Клиент вызывает ошибку. Для маленького проекта может быть такой подход проблем не принесёт, клиент может открыть консоль и легко найти ошибку, но проблема становится серьёзнее, если данный код часть большого приложения.

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

Дополнительные материалы

https://web-creator.ru/articles/solid_the_interface_segregation_principle
https://bit.ly/3xOVLhP
👍72
#SOLID

Введение

Продолжая тему SOLID. Сегодня мы рассмотрим последний принцип SOLID - принцип инверсии зависимостей.

Принцип инверсии зависимостей

Принцип инверсии зависимостей гласит:

Модули верхних уровней не должны импортировать сущности из модулей нижних уровней. Оба типа модулей должны зависеть от абстракций. Абстракции не должны зависеть от деталей Детали должны зависеть от абстракций.

Мо
дуль, может быть функцией, классом, файлом или просто фрагментом кода.

Пример

Представим у вас есть программа, которая, готовит стейки.

Мы написали вот такой код:


def cook():
steak = Steak()
steak.cook()


Данная функция не только готовит мясо, но и создаёт его.

Это нарушает принцип инверсии зависимостей.

Функция cook зависит от Steak.

Теперь представим, что мы начали готовить курочку. Здесь многие модифицировали функцию cook условием, которое проверяет тип продукта. Данный подход в корне неверен ведь для каждого продукта придётся добавлять по условию, что нарушает все принципы.
Попробуйте исправить данный код используя абстракцию и прислать решения в комментарии.

Дополнительные материалы

https://bit.ly/3xTszq8
https://habr.com/ru/post/313796/
👍5
#DRY

Введение

Сегодня мы рассмотрим принцип разработки ПО DRY.

DRY

DRY расшифровывается как Don’t repeat yourself(Не повторяйте себя), данный принцип нацелен на снижения количества повторяющегося кода в программе.

Принцип DRY гласит:

Каждая часть знания должна иметь единственное, непротиворечивое и авторитетное представление в рамках системы

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

Как соблюсти данный принцип и избежать излишней сложности?

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

WET

Нарушения принципа DRY называют WET расшифровывается как Write Everything Twice или We enjoy typing. Здесь есть игра слов - DRY(сухой) и WET(мокрый).

Дополнительные материалы

https://habr.com/ru/post/144611/
https://bit.ly/3N2Thkq
👍41
#ошибки

Введение

Сегодня я бы хотел рассмотреть топ 3 самых частых проблем в коде новичков.

F строки .format или % в тексте для компьютера

Мы питон программисты, привыкли форматировать текст с помощью ф строк, .format или % - это очень удобно, а в случае с ф строками ещё и очень быстро.
Если с текстом, который получает человек всё довольно просто, то с текстом для компьютера могут возникнуть проблемы.
Весь текст, ожидаемый компьютером текст должен быть отформатирован согласно определённым правилам, поэтому мы
не можем без последствий вставить произвольную строку. Что я подразумеваю под "текстом для компьютера"?
URL, SQL, любые языки разметки, регулярные выражения, код на питоне и других ЯПах, команды shell. О каких последствиях идёт речь?
Последствия могут быть разные, от банального краша программы, до серьёзной утечки данных.

Разработка не тестируемого кода

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

1. Использование глобальных переменных

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

2. config.py, settings.py, CFG, load_config

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

3. Использование синглтона

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

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

У каждого паттерна можно найти свои плюсы и минусы, однако синглтон - это исключение, так как его плюсы на самом деле и являются минусами.

Вывод: Не стоит использовать данный паттерн.

4. Использование функций высшего порядка там где они не нужны.

Вложенные функции невозможно протестировать и переиспользовать, поэтому использовать их нужно осторожно.

Всего я выделил 3 основных кейса, когда их использование необходимо:

1. Создание декоратора
2. Создание замыкания
3. Исключения, где без их использования нельзя создать нужное поведение в программе.

Использование ООП, где оно не нужно или использование псевдо ООП

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

class DeleteFile:
def delete(self):
....

Или процедурные куски кода засовываются в классы:

class Int_input(object):

def new(cls, quest_text, error_text: str = 'Вы ввели некорректное число'):
return cls.int_input(quest_text, error_text)

@staticmethod
def int_input(quest_text, error_text):
while True:
a = input(quest_text)
try:
return int(a)
except ValueError:
print(error_text)


Вывод: Если вы пишите маленький калькулятор на 20 строк или генератор паролей на 2, то не надо использовать ООП!

Дополнительные материалы

https://t.me/advice17/6
https://t.me/advice17/5
https://github.com/bomzheg/aiogram_template/blob/master/app/
👍6🤮51🤯1
#паттерны

Введение

Сегодня мы рассмотрим паттерны проектирования.

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

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

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

Виды паттернов

Паттерны делятся на 3 группы:

1. Поведенческие - паттерны проектирования, определяющие алгоритмы и способы реализации взаимодействия различных объектов и классов.
2. Порождающие - паттерны проектирования, которые имеют дело с процессом создания объектов.
3. Структурные - паттерны проектирования, в которых рассматривается вопрос о том, как из классов и объектов образуются более крупные структуры.

Когда использовать паттерн?

С паттернами нужно обращаться осторожно, ведь при не правильном использовании они могут очень сильно навредить.

Не нужно использовать паттерны просто так, чтобы они были в коде. Также, не стоит использовать паттерн, когда
он сильно усложняет весь код или приводит к нарушению какого-либо принципа ООП.

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

Анти-паттерны

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

То анти-паттерны - это шаблоны ошибок, которые совершаются при решении различных задач.

Ресурсы по изучению паттернов

Легендарная книга Design Patterns от банды четырёх: https://www.ozon.ru/product/patterny-obektno-orientirovannogo-proektirovaniya-gamma-erih-helm-richard-174491298/?sh=EDT-TraCsw
5👍2
#паттерны

Введение

Сегодня мы рассмотрим наш первый паттерн проектирования - стратегия.

Классификация

Тип: Поведенческий

Определение: Стратегия - это поведенческий паттерн проектирования, который определяет семейство схожих алгоритмов и помещает каждый из них в собственный класс, после чего алгоритмы можно взаимозаменять прямо во время исполнения программы.

Грубо говоря, его задача - выделить схожие алгоритмы, решающие конкретную задачу. 

Данный паттерн дает возможность в процессе выполнения выбрать стратегию (алгоритм, инструмент, подход) решения задачи. 

Как работает стратегия

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

Стратегии реализуют различные варианты алгоритма.

Клиент должен создать объект стратегию и передать её в контекст.

Чтобы сменить стратегию нужно будет всего лишь изменить объект стратегию в контексте.

Клиент будет вызывать нужные ему методу у контекста, а контекст будет делегировать их на стратегии.

Важное уточнение: Все стратегии должны быть унаследованы от одного абстрактного класса, чтобы контекст был независимым.

Плюсы данного паттерна:

Возможность замены алгоритмов на лету.

Реализует принцип открытости/закрытости

Уходит от наследования к делегированию

Предоставляет разделение между моделью и представлением

Минусы данного паттерна:

Клиент должен знать в чем разница между стратегиями.

Усложняет программу, из-за добавления новых классов.

Пример:

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

Задача

Придумайте несколько возможных вариантов валидации данных и реализуйте стратегию для примера, который дан выше.

Дополнительные материалы

https://refactoring.guru/ru/design-patterns/strategy

https://habr.com/ru/post/487858/
5👍1
#паттерны

Введение

Сегодня мы рассмотрим паттерн проектирования "Фабричный метод".

Классификация

Тип
: Порождающий

Определение: Фабричный метод - это порождающий паттерн проектирования, который определяет общий интерфейс для создания объектов в родителе, позволяя подклассам изменять тип создаваемых объектов.

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

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

Из чего состоит и как работает паттерн фабричный метод

1. Абстрактный класс/Интерфейс "создатель" - содержит фабричный метод и метод содержащий базовую бизнес логику, которая основана на объектах, возвращаемых фабричным методом. Метод с бизнес логикой обычно содержит вызов фабричного метода и некую работу с объектом, который вернул фабричный метод:

class BaseCreator(ABC):
@abstractmethod
def fabric_method(self):
"""
Return concrete product.
"""

def operation(self):
# Here some buiesness logic.
product = self
.fabric_method()

2. Абстрактный класс/Интерфейс "продукт" - методы которого реализуют конкретные продукты:

class BaseProduct(ABC):
@abstractmethod
def operation(self):
pass


3. Конкретные классы создатели и конкретные классы продуктов:

class ConcreteProduct1(BaseProduct):
def operation(self):
return "{Result of the ConcreteProduct1}"

class ConcreteProduct2(BaseProduct):
def operation(self):
return "{Result of the ConcreteProduct2}"

class ConcreteFactory1(BaseFactory):
def fabric_method(self):
return ConcreteProduct1()

class ConcreteFactory2(BaseFactory):
def fabric_method(self):
return ConcreteProduct2()


Плюсы данного паттерна:

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

Упрощает поддержку кода.

Реализует принцип открытости/закрытости

Минусы данного паттерна:

Может привести к созданию больших параллельных иерархий классов.

Пример и задача:

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

Пример из реального кода

Данный пример предоставил @Tishka17

https://github.com/Tishka17/dataclass_rest/blob/master/dataclass_rest/base.py#L24

Вот его объяснение какую задачу фабричный метод решает в данном случае: Это универсальный клиент для рестапишек. Ты реализуешь конкретные методы путем наследования и соответсвенно есть фабричный метод для создания конвертера (Factory из dataclass-factory)
👍6🔥6
Понятны ли вам посты по паттернам? Если нет, напишите в комментариях почему.
Anonymous Poll
20%
Да
20%
Нет
59%
Я оладушек
👍7
Навигация

Посты про ООП - #ООП
Посты про SOLID - #SOLID
Посты про ошибки и антипаттерны - #ошибки
Посты про паттерны проектирования - #паттерны
Посты про тайпхинты - #тайпхинты
Посты с задачами - #задача
Посты с исследованиями - #исследование
🔥9👍3
#паттерны

Введение

Сегодня мы рассмотрим паттерн проектирования "Строитель".

Классификация

Тип: По
рождающий

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

Строитель заменяет огромный метод инициализации, на класс со специальными методами, вызывая которые, вы создадите объект.

Данный паттерн позволяет пошагово инициализировать большие объекты!

Главное отличие «строителя» от «фабрики» — это то, что она используется, когда создание занимает один шаг, а «строитель» применяется при множестве шагов.

Из чего состоит и как работает паттерн строитель

1. Абстрактный класс/Интерфейс "Builder", содержит методы, которые должны быть реализованы в классе строитель.


class BaseBuilder(ABC):
@abstractmethod
def test1(self):
"""Return Test1 object"""

@abstractmethod
def test2(self):
"""Return Test1 object"""

@abstractmethod
def create_test(self):
"""Create test object"""


2.
Класс описывающий сложный объект который мы хотим создать.

class MyTest:
def init(self, test1: object, test2: object):
self.test1 = test1
self.test2 = test2

# Any methods that these object need.
def do(self):
...



3. Классы описывающие объекты, которые нужны для создания сложного объекта.


class Test1:
... # Here some logic

class Test2:
... # Here some logic



4. Класс строителя


class Builder(BaseBuilder):
def test1(self):
return Test1()

def test2(self):
return Test2()

def build_test(self):
test1 = self.test1()
test2 = self.test2()
return MyTest(test1, test2)


Плюсы данного паттерна

Позволяет создавать объекты пошагово.

Сокращает количество написанного кода и повышает читаемость

Изолирует код реализующий конструирование и представление.

Минусы данного паттерна

1. Процесс конструирования должен обеспечивать различные представления конструируемого объекта.

2. Алгоритм создания сложного объекта не должен зависеть от того, из каких частей состоит объект и как они стыкуются между собой.

Пример и задача:

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

Дополнительные материалы

https://m.youtube.com/watch?v=x5yGIuf13Rk1
https://bit.ly/3O7V8oB
👍3
#паттерны

Введение

Сегодня мы рассмотрим паттерн проектирования "Прототип".

Классификация

Тип: Порождающий

Определение: Прототип - это порождающий паттерн проектирования, который позволяет копировать объекты не вдаваясь в подробности их реализации.

Из чего состоит и как работает данный паттерн

1. Абстрактного класса/Интерфейс всех прототипов. Практически всегда содержит только один абстрактный метод - clone.


class BasePrototype(ABC):
@abstractmethod
def clone(self) -> object:
...



2. Конкретный прототип. Реализует операцию клонирования самого себя.


class Test(BasePrototype):
def init(self, test1, test2):
self.test1 = test1
self.test2 = test2

def clone(self):
return Test(self.test1, self.test2)


3. Клиент. Создаёт копии объектов.


test = Test(1, 2)
test2 = test.clone()



4. Опц
ионально - хранилище прототипов. В нём удобно хранить вариации объектов, которые по разному настроены.

В питоне данный паттерн с нуля писать не нужно - есть готовые методы copy.copy() и copy.deepcopy(), рекомендую использовать их.

Плюсы данного паттерна

1. Уменьшает количество кода, ускоряет создание объектов

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

Минусы данного паттерна

1. Прямое использование в клиенте может ухудшить читаемость кода.

Пример и задача

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

Дополнительные материалы

https://m.youtube.com/watch?v=Wm27d6Nn6VU
👍31
#паттерны

Введение

Сегодня мы рассмотрим паттерн проектирования "Адаптер".

Классификация

Тип:
Структурный

Определение: Адаптер — это структурный паттерн проектирования, который позволяет объектам с несовместимыми интерфейсами работать вместе.
Адаптер выступает прослойкой между объектами превращая вызовы одного в понятные для другого.

Очень удобно понять адаптер на примере из жизни. Представим человеку который говорит только на французском нужно рассказать историю человеку который понимает только немецкий. Сами понять они друг друга не смогут, поэтому им понадобится переводчик, который переведёт французкий на немецкий. В данном случае переводчик выступит адаптером.

Из чего состоит и как работает паттерн адаптер

1. Клиент. Описывает бизнес логику программы. Работает с адаптером и другими объектами.


adapter = Adapter()
result = adapter.do() + "5"


2. Интерфейс с которым может работать клиентский код.


class Test1:
def do(self) -> str:
return "3"


3. Класс, который имеет нужную клиенту логику, однако клиент не может с ним общаться, так как интерфейс данного класса ему не понятен.


class Test2:
def specific_do(self) -> float:
return 3.4


4. Адаптер - класс который помогает клиенту использовать функционал незнакомого ему сервиса в правильном формате. Реализует клиентский интерфейс. Содержит ссылку на незнакомый сервис. Адаптер при получении вызовов от клиента через клиентский интерфейс и переводит их в вызовы незнакомого сервиса в правильном формате.


class Adapter(Test1, Test2):
def do(self) -> str:
return f"Translated: {round(self.specific_do())}"


Уточнение: Приложение должно использовать объект адаптера только через клиентский интерфейс. Это позволит легко создавать и изменять адаптеры в будущем.

Плюсы данного паттерна

1. Скрывает все "низкоуровневые" преобразования интерфейсов от клиента. Реализует принцип абстракция.

Минусы данного паттерна

Таковых я не обнаружил

Пример и задача

В качестве примера возьмём класс с методом do который возвращает небольшой текст. Также есть класс с методом secret_do который возвращает другую строку, зашифрованную шифром Цезаря со сдвигом 5, которая ещё и полностью развёрнута.

В виде клиента выступает функция которая постит текст в ваш блог.(Можете просто создать функцию которая выводит переданный в неё текст). Естественно она должна принимать только расшифрованный текст.

Реализуйте адаптер для второго класса, который спасёт ваш блог от зашифрованных постов.

Пример из реального кода

https://github.com/aiogram/aiogram/blob/b190bbba1915ed3b7f311a780f34723ebd6b5acd/aiogram/contrib/fsm_storage/redis.py#L280

Вот его объяснение какую задачу решает адаптер в данном случае:

Здесь адаптер дает возможность контроллеру хранилища (RedisStorage) работать с первой версией редиса, т.е. адаптирует aioredis-v1, там еще есть адаптер для aioredis-v2, но он в отличие от первой версии адаптирует только создание клиента редиса


Дополнительные материалы

https://habr.com/ru/post/85095/
👍52
Введение

Давайте напишем простейшую программу на питоне:

print(0.1 + 0.2)

Не
опытные программисты с уверенностью ответят что выводом будет 0.3. Однако как вам известно это не так, выводом данной программы будет непонятное число с большим количеством нулей и четвёркой в конце:

0.30000000000000004

И
это проблема не питона, вы можете запустить такую же программу на js или c++. Вывод будет таким же.

Что же происходит на самом деле?

Давайте разбираться.

IEEE

Чем отличается целое число от дробного с точки зрения хранения?

Правильно в дробном числе есть точка. Точку не возможно представить с помощью нулей и единиц, поэтому нужна другая, специальная форма хранения такого числа. Данную задачу решил "Институт инженеров электроники и электротехники"(IEEE), он создал стандарт хранения дробных чисел - IEEE 754.

Как перевести дробное число из десятичной системы счисления в двоичную?

Перед тем как перейти к изучению стандарта IEEE 754 давайте рассмотрим как перевести дробное число из десятичной системы счисления в двоичную.

В качестве примера возьмём число 3.25. Чтобы перевести его в двоичную систему счисления, нужно перевести целую и дробную часть в двоичную систему поотдельности.

Начнём с целой части. Здесь все просто, мы делим число на 2 пока последнее частное не станет меньше двух. Последнее частное и все остатки записываем в обратном порядке.

Пример:

3 ÷ 2 = 1 (1)

3 в двоичной системе счисления - это 11

Перейдём к дробной части. Здесь вместо деления нам нужно умножение. Мы умножаем дробную часть на 2 пока не получим в ответе единицу. А затем просто склеиваем целые части ответов.

Пример:

0.25 * 2 = 0.5
0.5 * 2 = 1.0

0.25 в двоичной системе - это 01

IEEE 754

Итак, мы успешно перевели дробное число в двоичный вид: 11.01

Теперь нам нужно как-то хранить наше число. Для этого стандарт предлагает использовать экспоненцальную запись, которая соответствует, вот этой формуле:


(-1)^s*1.M*10^E

s
- знак числа
m - мантиса(дробная часть числа)
e - это количество знаков на которое нужно сдвинуть точку, чтобы перед ней осталась только одна единица.

Чтобы сохранить число в такой форме стандарт предлагает 3 основных формата хранения:

1. Одинарный - 32 бит
2. Двойной - 64 бит
3. Четырехкратный - 128 бит

Чем больше размер тем выше точность хранимого числа.

В качестве простого примера рассмотрим 32 битный формат.

Первый бит - бит знака. Если он равен нулю то число положительное, если он равен единице то число отрицательное.

Следующие 8 бит - выделены для хранения степени. Чтобы определить знак степени, нам нужно прибавить 127 к значению степени. Если число будет больше 127, то степень положительная, иначе - отрицательная.

Оставшиеся 23 бита - выделены для хранения мантиссы. Если мантисса меньше 23 бит, оставшиеся пространство заполняют нулями.

Вы можете спросить, а где хранится целая часть и основание степени?

Ответ прост - целая часть всегда равна единице, а основание степени всегда равно десяти => для экономии памяти их можно не хранить!

Давайте сохраним наше число в памяти.

1. Сдвинем точку на 1 знак влево 1.101. e = 1
2. Первый бит знака равен нулю, так как число положительное
3. e + 127 = 128 => число положительное.
4. Переведём 128 в двоичную систему счисления.

128 ÷ 2 = 64 (0)
64 ÷ 2 = 32 (0)
32 ÷ 2 = 16 (0)
16 ÷ 2 = 8 (0)
8 ÷ 2 = 4 (0)
4 ÷ 2 = 2 (0)
2 ÷ 2 = 1 (0)

128 в двоичной системе счисления -
10000000
5. Запишем 10000000 в 8 бит предназначеные для степени.
6. Запишем 101 в 23 бита предназначеные для хранения дробной части. Оставшиеся биты заполним нулями.
7. Мы сохранили наше число в памяти!

Вот результат выполнения всех шагов:


0 10000000 10100000000000000000000


Отлично, теперь попробуем перевести сохранённое число обратно:

s = 0 (знак положительный)
m = 101
e = 1

-1^s * 1.m * 10^e = 11.01

Осталось перевести 11.01 в десятичную систему счисления и мы получим наше число - 3.25.

Проблема

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

Пример:
👍6
0.8 * 2 = 1.6
0.6 * 2 = 1.2
0.2 * 2 = 0.4
0.4 * 2 = 0.8
...

Поэтому приходится записывать в дробную часть столько, сколько возможно.

После сохранения числа 0.8 в память. Мы получим, вот такой результат:


0 01111110 10011001100110011001101


А
при переводе данного числа обратно мы получаем непонятное число с большим количеством нулей и цифр:

0.800000011920928955078125

Теперь вы знаете, как на самом деле хранятся дробные числа и почему 0.1 + 0.2 != 0.3 с точки зрения компьютера.

Дополнительные материалы

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

Пример:


print(0.8) -> 0.8
print(f"{0.8:.20f}") -> 0.80000000000000004441


Когда нужны точные вычисления(например в области финансов), рекомендую использовать стандартную библиотеку decimal.

При каких-либо вычеслениях я настоятельно не рекомендую обрезать число, так как это приводит к ошибкам и потере точности.

https://docs.python.org/3/library/decimal.html
https://0.30000000000000004.com/
🔥7👍4