#ТаковПуть
Присваивания значений переменным
Все примеры ниже приведут к идентичному результату
Присваивания значений переменным
Все примеры ниже приведут к идентичному результату
x = 1
y = 2
x, y = 1, 2
x, y = [1, 2]
x, y = (1, 2)
x, y = {1, 2}
#ТаковПуть
Объединяем списки (list)
Объединяем списки (list)
fruits = ['яблоко','мандарин']
vegetables = ['лук', 'помидор']
meal = [*fruits, *vegetables]
print(meal)
#Output:
#['яблоко', 'мандарин', 'лук', 'помидор']
👍1
#ТаковПуть
Продолжаем примеры упаковки распаковки переменных
Упаковка в list и dict:
Продолжаем примеры упаковки распаковки переменных
Упаковка в list и dict:
a = [1, 2]
b = {3, 4}
c = (5, 6)
*list_data1, = *a, *b, *c
print(list_data1)
#Output: [1, 2, 3, 4, 5, 6]
*list_data2, = a, b, c
print(list_data2)
#Output: [[1, 2], {3, 4}, (5, 6)]
dict_data = dict(list_data2)
print(dict_data)
#Output: {1: 2, 3: 4, 5: 6}
👍1
#ВДзене
Друзья, коротко делюсь мыслями о новых статьях в Дзене
# 6 Работа с Git на Windows
В своих статьях я часто ссылаюсь на GitLab. Решил написать статью про установку, настройку и работу с Git и GitLab
Думаю, что надо написать ещё несколько статей про Git, CI/CD, ведение документации и автоматизацию с GitLab.
#git
# 7 Создание XLSB файла
Случайно обнаружил, что в интернете вообще нет пояснений, как создавать xlsb файлы на Python. Решил реализовать решение в виде готовой программы.
Скоро выпущу статью про скрапинг террористов, где эту программу применю.
#python #excel #pywin32
#8 Про совместную работу в Jupyter Notebook
Статья написана по памяти. На одной из моих работ было реализовано пространство для совместной работы в Jupyter, решил поделиться решением.
Единое пространство для работы на Python – очень полезная вещь. Как минимум, это позволяет подсматривать за кодингом коллег, что полезно в обучающих целях.
#python #team
Предыдущие статьи.
Друзья, коротко делюсь мыслями о новых статьях в Дзене
# 6 Работа с Git на Windows
В своих статьях я часто ссылаюсь на GitLab. Решил написать статью про установку, настройку и работу с Git и GitLab
Думаю, что надо написать ещё несколько статей про Git, CI/CD, ведение документации и автоматизацию с GitLab.
#git
# 7 Создание XLSB файла
Случайно обнаружил, что в интернете вообще нет пояснений, как создавать xlsb файлы на Python. Решил реализовать решение в виде готовой программы.
Скоро выпущу статью про скрапинг террористов, где эту программу применю.
#python #excel #pywin32
#8 Про совместную работу в Jupyter Notebook
Статья написана по памяти. На одной из моих работ было реализовано пространство для совместной работы в Jupyter, решил поделиться решением.
Единое пространство для работы на Python – очень полезная вещь. Как минимум, это позволяет подсматривать за кодингом коллег, что полезно в обучающих целях.
#python #team
Предыдущие статьи.
👍1
#ЧистыйКот
Короткие функции
Важно писать код, который легко читается и понимается. Один из способов достижения этой цели — создание коротких функций.
Старайтесь, чтобы ваши функции содержали до 15 строк (а лучше до 5) и выполняли только одну задачу.
К примеру, в моем недавнем проекте я мог бы создать один метод, который бы создавал Application, Workbook и Sheet эксель файла.
Но для большей наглядности я разделил три действия на три метода.
Короткие функции
Важно писать код, который легко читается и понимается. Один из способов достижения этой цели — создание коротких функций.
Старайтесь, чтобы ваши функции содержали до 15 строк (а лучше до 5) и выполняли только одну задачу.
К примеру, в моем недавнем проекте я мог бы создать один метод, который бы создавал Application, Workbook и Sheet эксель файла.
def _create_app( self ) -> None:
self.xl_app = ...
...
self.xl_wb = ...
self.xl_sheet = ...
Но для большей наглядности я разделил три действия на три метода.
def _create_new_app( self ) -> None:
self.xl_app = ...
self.xl_app.Visible = True
self.xl_app.ScreenUpdating = True
def _create_new_wb( self ) -> None:
self.xl_wb = ...
def _activate_sheet( self ) -> None:
self.xl_sheet = ...
#ВДзене
Друзья, коротко делюсь мыслями о новых статьях в Дзене
Все программы в статьях намеренно написаны в разных архитектурных решениях.
В программах очень много регулярок, также показаны различные подходы работы с ними, еще реализованы два варианта поиска расстояния Левенштейна.
# 9 Скрапинг перечня террористов ФЛ
В рамках статьи наполняем эксель файл данными с сайта.
#python #selenium #excel #pywin32
# 10 Скрапинг перечня террористов ЮЛ
В рамках статьи разворачиваем БД, которую наполняем данными с сайта.
#python #selenium #PostgreSQL #pyodbc
# 11 Нахождение террориста в ЧС ФЛ
Показываю интересный подход к поиску человека в ЧС, когда в рамках одного ФИО делаем сотни различных проверок.
#python #excel #re #pyxlsb #pydantic
# 12 Нахождение террориста в ЧС ЮЛ
Показываю немного другой подход к поиску в черном списке.
#python #PostgreSQL #re #pyodbc #pydantic
🎯 В планах: на базе этих программ сделать API, а на базе него web-сервис с UI и авторизацией.
Предыдущие статьи.
Друзья, коротко делюсь мыслями о новых статьях в Дзене
Все программы в статьях намеренно написаны в разных архитектурных решениях.
В программах очень много регулярок, также показаны различные подходы работы с ними, еще реализованы два варианта поиска расстояния Левенштейна.
# 9 Скрапинг перечня террористов ФЛ
В рамках статьи наполняем эксель файл данными с сайта.
#python #selenium #excel #pywin32
# 10 Скрапинг перечня террористов ЮЛ
В рамках статьи разворачиваем БД, которую наполняем данными с сайта.
#python #selenium #PostgreSQL #pyodbc
# 11 Нахождение террориста в ЧС ФЛ
Показываю интересный подход к поиску человека в ЧС, когда в рамках одного ФИО делаем сотни различных проверок.
#python #excel #re #pyxlsb #pydantic
# 12 Нахождение террориста в ЧС ЮЛ
Показываю немного другой подход к поиску в черном списке.
#python #PostgreSQL #re #pyodbc #pydantic
🎯 В планах: на базе этих программ сделать API, а на базе него web-сервис с UI и авторизацией.
Предыдущие статьи.
#ТаковПуть
Уникальные элементы list. ч.1
Довольно часто требуется получить уникальный набор элементов списка.
Возьмём список:
Самый простой и популярный способ - это получить set элементов:
Проблема в том, что теряется порядок записей.
Можно проитерироваться по элементам list и добавлять уникальные элементы в другой list:
Порядок сохранен, но надо заранее создать пустой list.
А можно применить магию. И преобразовать list в dict и вернуть list ключей:
Результат тот же, а кода меньше.
Уникальные элементы list. ч.1
Довольно часто требуется получить уникальный набор элементов списка.
Возьмём список:
l = ["Атос", "Портос", "Арамис", "д’Артаньян", "д’Артаньян"]
Самый простой и популярный способ - это получить set элементов:
u = set(l)
print(u)
# Output:
# {'д’Артаньян', 'Арамис', 'Атос', 'Портос'}
Проблема в том, что теряется порядок записей.
Можно проитерироваться по элементам list и добавлять уникальные элементы в другой list:
u = list()
[u.append(k) for k in l if k not in u]
print(u)
# Output:
# ['Атос', 'Портос', 'Арамис', 'д’Артаньян']
Порядок сохранен, но надо заранее создать пустой list.
А можно применить магию. И преобразовать list в dict и вернуть list ключей:
u = [*dict.fromkeys(l)]
print(u)
# Output:
# ['Атос', 'Портос', 'Арамис', 'д’Артаньян']
Результат тот же, а кода меньше.
👍1
#ТаковПуть
Уникальные элементы list. ч.2
Возьмём список из первой части и для каждого имени создадим экземпляр класса Person, который будет содержать только это имя:
Все примеры из ч.1 не будут работать 😱
К примеру:
Не только порядок потерялся, еще и дубли не убрались 😢
Чтобы исправить ситуацию, необходимо доработать класс Person
Проверяем:
Дубли убрались и порядок остался 😎
Уникальные элементы list. ч.2
Возьмём список из первой части и для каждого имени создадим экземпляр класса Person, который будет содержать только это имя:
class Person:
def __init__(self, name):
self._name = name
def __repr__(self):
return self._name
names = ["Атос", "Портос", "Арамис", "д’Артаньян", "д’Артаньян"]
persons = []
for name in names:
persons.append(Person(name))
print(persons)
# Output:
# [Атос, Портос, Арамис, д’Артаньян, д’Артаньян]
Все примеры из ч.1 не будут работать 😱
К примеру:
u = set(persons)
print(u)
# Output:
# {Портос, Атос, д’Артаньян, Арамис, д’Артаньян}
Не только порядок потерялся, еще и дубли не убрались 😢
Чтобы исправить ситуацию, необходимо доработать класс Person
class Person:
def __init__(self, name):
self._name = name
def __repr__(self):
return self._name
def __eq__(self, other):
if isinstance(other, Person):
return self._name == other._name
else:
return False
def __hash__(self):
return hash(self._name)
Проверяем:
u = [*dict.fromkeys(persons)]
print(u)
# Output:
# ['Атос', 'Портос', 'Арамис', 'д’Артаньян']
Дубли убрались и порядок остался 😎
👍1
#ТаковПуть
Возможности конструкции match-case в Python, ч.1
Конструкция представляет собой более мощный инструмент, чем аналоги в других языках.
Ниже приведен пример обработки словаря.
Подадим словарь, где будет ключ name
Сработал второй блок case.
Первый case был пропущен, т.к. нет поля age
Как видно из примера, словарь не должен на 100% совпадать по ключам, что делает конструкцию match-case очень интересным инструментом в разработке и отладке программ.
Теперь добавим ключ age
Сработал первый блок case.
Теперь подадим организацию, а не физ лицо
Сработал дефолтный блок case
Спасибо разработчикам Python за такой прекрасный инструмент, который убирает из кода громоздкие конструкции if-else
Возможности конструкции match-case в Python, ч.1
Конструкция представляет собой более мощный инструмент, чем аналоги в других языках.
Ниже приведен пример обработки словаря.
def match_dict( data : dict ) -> None:
match data:
case {"name": name, "age": age}:
print(f"Имя: {name}, Возраст: {age}")
case {"name": name}:
print(f"Имя: {name}")
case _:
print("Имя не найдено")
Подадим словарь, где будет ключ name
d = {"id": 1, "name": "Вася"}
match_dict(d)
# Output:
# Имя: ВасяСработал второй блок case.
Первый case был пропущен, т.к. нет поля age
Как видно из примера, словарь не должен на 100% совпадать по ключам, что делает конструкцию match-case очень интересным инструментом в разработке и отладке программ.
Теперь добавим ключ age
d = {"id": 1, "name": "Вася", "age": 19}
match_dict(d)
# Output:
# Имя: Вася, Возраст: 19Сработал первый блок case.
Теперь подадим организацию, а не физ лицо
d = {"id": 1, "orgName": "ООО Ромашка", "inn": "1234567890"}
match_dict(d)
# Output:
# Имя не найденоСработал дефолтный блок case
Спасибо разработчикам Python за такой прекрасный инструмент, который убирает из кода громоздкие конструкции if-else
👍1
#ТаковПуть
Возможности конструкции match-case в Python, ч.2
В примере ниже показано, как можно с помощью конструкции match-case обрабатывать разные типы переменной
Конструкцией type мы создали пользовательский тип.
Причем этот тип рекурсивный, т.е. ссылается сам на себя.
Фактически был создан новый тип: Целое число или список, содержащий числа и другие списки.
Функция принимает на вход этот тип данных.
С помощью конструкции match-case маршрутизируется логика программы
Как итог, программа рекурсивно печатает все числа во вложенных списках.
Возможности конструкции match-case в Python, ч.2
В примере ниже показано, как можно с помощью конструкции match-case обрабатывать разные типы переменной
type NestedIntItem = int | list[NestedIntItem]
def print_list( data: NestedIntItem ) -> None:
match data:
case int():
print(data)
case list():
for item in data:
print_list(item)
case _:
raise TypeError(f"Expected int or list, got {type(data)}")
nested_list: NestedIntItem = [5,
[3, 7],
[9, [0, 1]],
2,
8,
[4, 6]
]
print_list(nested_list)
# output
# 5
# 3
# 7
# 9
# 0
# 1
# 2
# 8
# 4
# 6
Конструкцией type мы создали пользовательский тип.
Причем этот тип рекурсивный, т.е. ссылается сам на себя.
Фактически был создан новый тип: Целое число или список, содержащий числа и другие списки.
Функция принимает на вход этот тип данных.
С помощью конструкции match-case маршрутизируется логика программы
Как итог, программа рекурсивно печатает все числа во вложенных списках.
👍1
#ТаковПуть
Валидация полей класса. ч.1 Подготовка.
Есть множество различных вариаций, как валидировать поля.
Тут показан один из самых надежных.
Для усложнения примера возьмем не один класс, а два класса.
Класс Client будет наследоваться от BaseModel библиотеки pydantic.
Класс ClientData будет наследовать поля из класса Client и все возможности класса BaseModel.
Так как мы наследуемся от BaseModel, у нас появляется в руках мощный инструмент валидации полей.
Напишем базовый код:
Пока у нас нет валидации полей, кроме валидации типов.
Например, в поле ids мы не сможем написать str
В следующей части реализуем 3 разных варианта валидации.
Валидация полей класса. ч.1 Подготовка.
Есть множество различных вариаций, как валидировать поля.
Тут показан один из самых надежных.
Для усложнения примера возьмем не один класс, а два класса.
Класс Client будет наследоваться от BaseModel библиотеки pydantic.
Класс ClientData будет наследовать поля из класса Client и все возможности класса BaseModel.
Так как мы наследуемся от BaseModel, у нас появляется в руках мощный инструмент валидации полей.
Напишем базовый код:
from pydantic import BaseModel, ValidationError, field_validator
class Client(BaseModel):
name: str
class ClientData(Client):
ids: list[int]
phone_type: str
phone: str
# Тут будем писать весь дальнейший код
try:
product =ClientData(name="Peter", ids=[100], phone_type="mobile", phone="9123456")
print(product)
except ValidationError as err:
print(err)
# output
# name='Peter' ids=[100] phone_type='mobile' phone='9123456'
Пока у нас нет валидации полей, кроме валидации типов.
Например, в поле ids мы не сможем написать str
В следующей части реализуем 3 разных варианта валидации.
👍1
#ТаковПуть
Валидация полей класса. ч.2 Валидация
1. Преобразование типа
Если в поле ids передадим не list, а int, программа сама должна преобразовать int в list
Очень важно указать mode="before", если указать mode="after", то программа упадет, т.к. ids обязан быть list.
Разница между mode="before" и mode="after" в том, что при mode="before" программа вызывает метод ДО проверки типов класса, что позволяет методу работать с любым типом данных, а не только тем, что обязан подаваться на вход.
2. Валидируем несколько полей один за другим.
Цикл скрыт от нас. Но он есть и поочередно проверяет на кол-во символов сначала поле name, потом phone_type, потом phone.
Так как именно в таком порядке поля инициализированы в классе ClientData.
В методе появилось поле info, оно содержит объект ValidationInfo.
Он содержит информацию о полях класса ClientData, которые уже прошли валидацию.
И содержит информацию о поле, которое проходит валидацию.
3. Валидируем поле, опираясь на ранее провалидированное поле
К моменту, когда начинается валидация поля phone, уже провалидировано поле phone_type.
Поэтому в рамках примера мы можем реализовать проверку типа: если это мобильный телефон, то он должен начинаться на 9
Также в декораторе не указали mode, это значит, что используем дефолтный, т.е. mode="after".
Валидация полей класса. ч.2 Валидация
1. Преобразование типа
Если в поле ids передадим не list, а int, программа сама должна преобразовать int в list
@field_validator('ids', mode="before")
@classmethod
def check_id( cls, value ):
if not isinstance(value, list):
return [value]
else:
return valueОчень важно указать mode="before", если указать mode="after", то программа упадет, т.к. ids обязан быть list.
Разница между mode="before" и mode="after" в том, что при mode="before" программа вызывает метод ДО проверки типов класса, что позволяет методу работать с любым типом данных, а не только тем, что обязан подаваться на вход.
2. Валидируем несколько полей один за другим.
@field_validator('name', 'phone', 'phone_type', mode="after")
@classmethod
def check_len( cls, value, info ):
if len(value) <= 2:
raise ValueError(f'Поле {info.field_name} должно быть длинее 2х символов')
return valueЦикл скрыт от нас. Но он есть и поочередно проверяет на кол-во символов сначала поле name, потом phone_type, потом phone.
Так как именно в таком порядке поля инициализированы в классе ClientData.
В методе появилось поле info, оно содержит объект ValidationInfo.
Он содержит информацию о полях класса ClientData, которые уже прошли валидацию.
И содержит информацию о поле, которое проходит валидацию.
3. Валидируем поле, опираясь на ранее провалидированное поле
@field_validator('phone')
@classmethod
def check_phone( cls, value, info ):
phone_type = info.data['phone_type']
if phone_type == 'mobile':
if not value.startswith("9"):
raise ValueError('Мобильный должен начинаться с 9')
return valueК моменту, когда начинается валидация поля phone, уже провалидировано поле phone_type.
Поэтому в рамках примера мы можем реализовать проверку типа: если это мобильный телефон, то он должен начинаться на 9
Также в декораторе не указали mode, это значит, что используем дефолтный, т.е. mode="after".
👍1
#ТаковПуть
Класс контейнер
Создаем контейнер Orders, который будет содержать список из классов Order
Контейнер будет:
- итерируемый
- с поддержкой срезов
- с разными поисками элементов контейнера
- с проверкой типов
- с возможностью добавления элементов
- с проверкой размерности контейнера
Код широкий, лучше просматривать горизонтально.
Класс контейнер
Создаем контейнер Orders, который будет содержать список из классов Order
Контейнер будет:
- итерируемый
- с поддержкой срезов
- с разными поисками элементов контейнера
- с проверкой типов
- с возможностью добавления элементов
- с проверкой размерности контейнера
Код широкий, лучше просматривать горизонтально.
from typing import Sequence, Iterator, Optional, List
from dataclasses import dataclass
@dataclass
class Order:
id: int
def __repr__(self) -> str:
return f"order_id={self.id}"
class Orders:
def __init__(self, orders: Sequence[Order]) -> None:
if not all(isinstance(o, Order) for o in orders):
raise TypeError("Все элементы должны быть типа Order")
self._orders = list(orders)
def __getitem__(self, key: int | slice) -> Order | List[Order]:
if isinstance(key, slice):
return self._orders[key]
if not isinstance(key, int):
raise TypeError("Индекс должен быть целым числом или срезом")
if not 0 <= key < len(self._orders):
raise IndexError(f"Индекс {key} вне диапазона [0, {len(self._orders)})")
return self._orders[key]
def __len__(self) -> int:
return len(self._orders)
def __iter__(self) -> Iterator[Order]:
return iter(self._orders)
def __contains__(self, item: object) -> bool:
return item in self._orders
def get_by_id(self, id: int) -> Optional[Order]:
return next((o for o in self._orders if o.id == id), None)
def add_order(self, order: Order) -> None:
if not isinstance(order, Order):
raise TypeError("Можно добавлять только объекты Order")
self._orders.append(order)
if __name__ == "__main__":
orders = Orders([Order(1), Order(2), Order(3)])
print("Все заказы:")
for order in orders:
print(order)
print("Первый заказ:", orders[0])
print("Количество заказов:", len(orders))
print("Есть ли заказ с id=2:", Order(2) in orders)
print("Поиск заказа с id=3:", orders.get_by_id(3))
print("Поиск несуществующего заказа:", orders.get_by_id(99))
# Добавление нового заказа
orders.add_order(Order(4))
print("После добавления:", [o.id for o in orders])
print("Последние 2 заказа:")
for order in orders[-2:]:
print(order)
#ТаковПуть
Структурная типизация и причём тут утки. ч.1🦆🦆🦆
Структурная типизация - это подход в системе типов, где совместимость типов определяется их структурой (наличием определенных атрибутов и методов), а не явным наследованием или указанием типа.
Структурная типизация - это продвинутая версия классической утиной типизации:
Пример:
Как видно на примере, т.к. у Person есть метод quack, то метод speak прекрасно его применил и человек крякнул.😜
Структурная типизация работает на схожем подходе. Для её реализации нам необходимо создать класс, наследуемый от Protocol
Теперь посмотрим работу с таким классом:
Функции avg и med реализуют интерфейс метода call, поэтому они прекрасно работают.
А вот функция hello возвращает не float, а str.
Так как мы работаем в Python, а это язык с динамической типизацией, то функция hello прекрасно отработает и ошибки не будет.
Но анализаторы кода (типа mypy) выдадут ошибку.
Желательно писать код так, чтобы анализаторы кода не жаловались:)
Структурная типизация и причём тут утки. ч.1🦆🦆🦆
Структурная типизация - это подход в системе типов, где совместимость типов определяется их структурой (наличием определенных атрибутов и методов), а не явным наследованием или указанием типа.
Структурная типизация - это продвинутая версия классической утиной типизации:
"Если это ходит как утка и крякает как утка, то это утка"
Пример:
class Bird:
def quack(self):
print("Кря!")
class Person:
def quack(self):
print("Кря!")
def speak(obj):
obj.quack()
speak(Bird()) # Кря!
speak(Person()) # Кря!
Как видно на примере, т.к. у Person есть метод quack, то метод speak прекрасно его применил и человек крякнул.😜
Структурная типизация работает на схожем подходе. Для её реализации нам необходимо создать класс, наследуемый от Protocol
Теперь посмотрим работу с таким классом:
from typing import Protocol, List
import statistics
class Stat(Protocol):
def __call__(self, items: List[int]) -> float:
...
def avg( items: List[int] ) -> float:
return sum(items) / len(items)
def med(items: List[int]) -> float:
return statistics.median(items)
def hello(items: List[int]) -> str:
return "Hello"
def stat(op: Stat, items: List[int]) -> float:
return op(items)
# Список int для примера
numbers = [1, 2, 3, 4, 5]
avg = stat(avg, numbers)
median = stat(med, numbers)
hi = stat(hello, numbers) # ошибка: Expected type 'Stat', got '(items: list[int]) -> str' instead
print(f"Среднее: {avg}") # Среднее: 3.0
print(f"Медиана: {median}") # Медиана: 3
Функции avg и med реализуют интерфейс метода call, поэтому они прекрасно работают.
А вот функция hello возвращает не float, а str.
Так как мы работаем в Python, а это язык с динамической типизацией, то функция hello прекрасно отработает и ошибки не будет.
Но анализаторы кода (типа mypy) выдадут ошибку.
Желательно писать код так, чтобы анализаторы кода не жаловались:)
#ТаковПуть
Структурная типизация и причём тут утки. ч.2 🦆🦆🦆
Если в прошлый раз мы работали с функциями, тут поработаем с классами.
Пусть будет класс Order, реализация в нем нам неважна, поэтому оставим его пустым.
Теперь реализуем класс OrderStorage, у которого будет метод save
Создадим 2 класса, которые будут реализовывать структуру класса OrderStorage
Создадим функцию, которая будет принимать на вход Order и класс, который будет реализовывать структуру класс OrderStorage
За счет того, что DBStorage и FileStorage реализуют структуру OrderStorage, то функция save_order прекрасно обрабатывает эти классы.
Полный код:
Ниже представлен аналогичный пример, но в другой реализации:
Строка
нужна, чтоб код запустить в одном файле.
Структурная типизация и причём тут утки. ч.2 🦆🦆🦆
Если в прошлый раз мы работали с функциями, тут поработаем с классами.
Пусть будет класс Order, реализация в нем нам неважна, поэтому оставим его пустым.
class Order:
pass
order = Order()
Теперь реализуем класс OrderStorage, у которого будет метод save
from typing import Protocol
class OrderStorage(Protocol):
def save(self, order: Order) -> None:
pass
Создадим 2 класса, которые будут реализовывать структуру класса OrderStorage
class DBStorage:
def save(self, order: Order) -> None:
print("Заказ сохранили в БД")
class FileStorage:
def save(self, order: Order) -> None:
print("Заказ сохранили в файл")
db_storage = DBStorage()
file_storage = FileStorage()
Создадим функцию, которая будет принимать на вход Order и класс, который будет реализовывать структуру класс OrderStorage
save_order(order, db_storage)
save_order(order, file_storage)
За счет того, что DBStorage и FileStorage реализуют структуру OrderStorage, то функция save_order прекрасно обрабатывает эти классы.
Полный код:
from typing import Protocol
class Order:
pass
order = Order()
class OrderStorage(Protocol):
def save(self, order: Order) -> None:
pass
class DBStorage:
def save(self, order: Order) -> None:
print("Заказ сохранили в БД")
class FileStorage:
def save(self, order: Order) -> None:
print("Заказ сохранили в файл")
db_storage = DBStorage()
file_storage = FileStorage()
save_order(order, db_storage)
save_order(order, file_storage)
Ниже представлен аналогичный пример, но в другой реализации:
from __future__ import annotations
from typing import Protocol
class OrderStorage(Protocol):
def save(self, order: Order) -> None:
pass
class Order:
def __init__(self, *storages: OrderStorage) -> None:
self.storages = storages
def save_order(self) -> None:
for storage in self.storages:
storage.save(self)
class DBStorage:
def save(self, order: Order) -> None:
print("Заказ сохранили в БД")
class FileStorage:
def save(self, order: Order) -> None:
print("Заказ сохранили в файл")
db_storage = DBStorage()
file_storage = FileStorage()
order = Order(db_storage, file_storage)
order.save_order()
Строка
from __future__ import annotations
нужна, чтоб код запустить в одном файле.
#ЗаЧаем
Цитата из книги "Вы, конечно, шутите, мистер Фейнман!", посвящена участнику проекта Манхеттен
В ответ на эту цитату, цитата преподавателя МГУ Столярова Андрея Викторовича из книги "Программирование. Введение в профессию"
Цитата из книги "Вы, конечно, шутите, мистер Фейнман!", посвящена участнику проекта Манхеттен
А что касается мистера Френкеля, который затеял всю эту деятельность, то он начал страдать от компьютерной болезни — о ней сегодня знает каждый, кто работал с компьютерами. Это очень серьезная болезнь, и работать при ней невозможно. Беда с компьютерами состоит в том, что ты с ними играешь. Они так прекрасны, столько возможностей — если четное число, делаешь это, если нечетное, делаешь то, и очень скоро на одной-единственной машине можно делать все более и более изощренные вещи, если только ты достаточно умен.
В ответ на эту цитату, цитата преподавателя МГУ Столярова Андрея Викторовича из книги "Программирование. Введение в профессию"
Как ни странно, именно эта “болезнь” превращает человека в программиста. Если хотите стать программистом — постарайтесь подхватить болезнь, описанную Фейнманом.
#ТаковПуть
Типизация.
Можно написать простой метод
А можно написать аналогичный метод с типизацией
Типизация призвана повысить читаемость кода.
Также типизация существенно ускоряет программу
Типизация.
Можно написать простой метод
def print_phone(phone = None):
if phone:
print(f"Phone number is {phone}")
else:
print("No phone number")
print_phone() #No phone number
А можно написать аналогичный метод с типизацией
def print_phone(phone: str | None = None) -> None:
if phone:
print(f"Phone number is {phone}")
else:
print("No phone number")
print_phone() #No phone number
Типизация призвана повысить читаемость кода.
Также типизация существенно ускоряет программу
#ТаковПуть
В Python есть прекрасные функции: all() и any(), которые делают код чище и эффективнее.
Классический пример, который показывает базовую работу этих функций:
any() находит первый подходящий элемент, all() проверяет, что все элементы удовлетворяют условию
Пример ниже показывает вызов функций в выражении, что удобно для комплексных проверок, т.е. когда много проверок внутри одной проверки.
Примером ниже показываю работу с генераторами:
На самом деле, последний yield даже не вызвался. Это связано с тем, что функции all() и any() - ленивые.
Как работают ленивые функции показываю в примере ниже:
🎯 Наглядная демонстрация принципа "короткого замыкания". any() выполнила только первую проверку, all() остановилась на третьей, не проверяя четвертую. Это важно для снижения вычислительной нагрузки и ускорения кода.
В Python есть прекрасные функции: all() и any(), которые делают код чище и эффективнее.
Классический пример, который показывает базовую работу этих функций:
nums = [2, 4, 5, 6]
any_1 = any(n % 2 == 0 for n in nums)
all_1 = all(n % 2 == 0 for n in nums)
print(any_1) #True
print(all_1) #False
any() находит первый подходящий элемент, all() проверяет, что все элементы удовлетворяют условию
Пример ниже показывает вызов функций в выражении, что удобно для комплексных проверок, т.е. когда много проверок внутри одной проверки.
def check_1():
return True
def check_2():
return True
def check_3():
return False
def check_4():
return True
checks_2 = [check_1, check_2, check_3, check_4]
any_2 = any(check() for check in checks_2)
all_2 = all(check() for check in checks_2)
print(any_2) #True
print(all_2) #False
Примером ниже показываю работу с генераторами:
def checks_3():
yield True
yield True
yield False
yield True
any_3 = any(check for check in checks_3())
all_3 = all(check for check in checks_3())
print(any_3) #True
print(all_3) #False
На самом деле, последний yield даже не вызвался. Это связано с тем, что функции all() и any() - ленивые.
Как работают ленивые функции показываю в примере ниже:
def checks_4():
print("#1") # any() остановится здесь и не пойдет дальше
yield True
print("#2")
yield True
print("#3") # all() остановится здесь и не пойдет дальше
yield False
print("#4")
yield True
print("any")
any(check for check in checks_4())
print("all")
all(check for check in checks_4())
# Output:
# any
# #1
# all
# #1
# #2
# #3
🎯 Наглядная демонстрация принципа "короткого замыкания". any() выполнила только первую проверку, all() остановилась на третьей, не проверяя четвертую. Это важно для снижения вычислительной нагрузки и ускорения кода.
👍1
#ТаковПуть
Кратко о bool
С удивлением обнаружил, что нельзя наследоваться от bool.
Как понимаю, причина в том, что разработчики языка сознательно идут на это ограничение ради надежности языка.
Несмотря на невозможность наследоваться от bool, можно использовать магические методы для превращения своего класса в bool. 🪄🧙♂
Жизненный пример, где в классе магический метод bool не будет лишним
Есть еще другой вариант написания кода, но он считается небезопасным, т.к. объект неожиданно становится bool.
Я не знаю, где такое может пригодится, но если очень хочется, чтоб вызов класса возвращал bool, то есть техническая возможность такое сделать.
Кратко о bool
С удивлением обнаружил, что нельзя наследоваться от bool.
Как понимаю, причина в том, что разработчики языка сознательно идут на это ограничение ради надежности языка.
Несмотря на невозможность наследоваться от bool, можно использовать магические методы для превращения своего класса в bool. 🪄🧙♂
class IsEmpty:
def __init__(self, sequence):
self._sequence = sequence
def __bool__(self):
return not bool(self._sequence)
# Примеры разных последовательностей
sec_1 = [1, 2, 3]
sec_2 = {"a":1, "b":2}
sec_3 = {}
# три разных варианта получить bool
result_1 = IsEmpty(sec_1).__bool__()
result_2 = IsEmpty(sec_2)
result_3 = IsEmpty(sec_3)
print(result_1) # False
print(bool(result_2)) # False
if result_3: # автоматом вызовется __bool__
print("Пустой") # вызовет этот вариант
else:
print("Не пустой")
Жизненный пример, где в классе магический метод bool не будет лишним
class DatabaseConnection:
def __bool__(self):
return self.is_connected()
...
# такой вариант неидеоматичен
with DatabaseConnection(...) as conn:
if conn.is_connected():
...
# такой вариант предпочтительнее
with DatabaseConnection(...) as conn:
if conn:
...
Есть еще другой вариант написания кода, но он считается небезопасным, т.к. объект неожиданно становится bool.
def bool_class(cls):
def wrapper(*args):
result = cls(*args)
return bool(result)
return wrapper
@bool_class
class IsEmpty:
def __init__(self, sequence):
self._sequence = sequence
def __bool__(self):
return not bool(self._sequence)
# Примеры разных последовательностей
sec_1 = [1, 2, 3]
sec_2 = {"a":1, "b":2}
sec_3 = {}
result_1 = IsEmpty(sec_1)
result_2 = IsEmpty(sec_2)
result_3 = IsEmpty(sec_3)
print(result_1) # False
print(result_2) # False
print(result_3) # True
# причем принтуются именно булевые значения
print(result_3 is True) # True
Я не знаю, где такое может пригодится, но если очень хочется, чтоб вызов класса возвращал bool, то есть техническая возможность такое сделать.
#ТаковПуть
Практикуемся с типами
Начнем с примера:
В примере создаётся тип-алиас Order, который является просто псевдонимом для str
Это делается для того, чтобы не просто у некой переменной был тип, а чтобы он был осмысленный.
Продолжим. Сделаем не просто алиас, а еще и произведем проверку на наполнение.
Теперь создадим новый тип Order, который наследуется от str
Создали новый тип, а не просто алиас и при этом сохранили всю функциональность str
Расширим пример. Добавим метод для нашего типа
Т.е. был создан пользовательский тип данных со встроенным методом!
Практикуемся с типами
Начнем с примера:
type Order = str
order: Order = "123"
print(type(order)) # <class 'str'>
В примере создаётся тип-алиас Order, который является просто псевдонимом для str
Это делается для того, чтобы не просто у некой переменной был тип, а чтобы он был осмысленный.
Продолжим. Сделаем не просто алиас, а еще и произведем проверку на наполнение.
from typing import NewType
Order = NewType('Order', str)
def validate_order(order_str: str) -> Order:
if not order_str.isdigit():
raise ValueError("Ожидается число")
return Order(order_str)
order: Order = validate_order("123")
print(type(order)) # <class 'str'>, но тип аннотирован как Order
Теперь создадим новый тип Order, который наследуется от str
Order = type('Order', (str,), {})
order: Order = Order("123")
print(type(order)) # <class '__main__.Order'>
print(isinstance(order, str)) # TrueСоздали новый тип, а не просто алиас и при этом сохранили всю функциональность str
Расширим пример. Добавим метод для нашего типа
def order_format(self):
return f"Order: {self}"
Order = type('Order', (str,), {'format': order_format})
order = Order("123")
print(order.format()) # "Order: 123"
Т.е. был создан пользовательский тип данных со встроенным методом!
👍1