Бизнес блог #1
Выжимаю книги до самой сути.
👉 Реклама - @jaMasha
📇 Хотите свою книгу? Мы напишем её за вас и сделаем книгу бестселлером. Подробности в боте @Summary_library_bot
🏆 Оставьте след в истории с помощью книги
https://expert-book.pro
Фильмы и сериалы со всей планеты. Мы знаем, что посмотреть, где посмотреть и на что сходить в кино.
Last updated 8 hours ago
Все материалы размещены по партнёрской програме ivi.ru | All materials are posted on the partner program ivi.ru
По всем вопросам: @kuzr103
Купить рекламу: https://telega.in/c/k1noxa103
Основной канал: https://t.me/kino_hd2
Last updated 1 month, 2 weeks ago
Объекты datetime.timedelta
поддерживают операторы деления и умножения
```
from datetime import timedelta
td1 = timedelta(hours=1)
# увеличим интервал в 2.5 раза
print(td1*2.5)
# 2:30:00
# разделим интервал на 2
print(td1/2)
# 0:30:00
```
Можно разделить один интервал на другой, включая целочисленное деление. Так мы узнаем сколько раз один период помещается в другой.
td2 = timedelta(minutes=25)
print(td1/td2)
\# 2.4
print(td1//td2)
\# 2
А так же остаток от делния.
print(td1%td2)
\# 0:10:00
Объекты datetime.timedelta
поддерживают отрицательные значения. Эти две записи идентичны.
datetime.now() \- timedelta(hours=1)
datetime.now() + timedelta(hours=\-1)
И, что очевидно, операторы сравнения
td1>td2
\# True
А еще можно почитать про форматирование даты и времени здесь и здесь.
Telegram
Python Заметки
Все знают как красиво написать дату и время с помощью библиотеки datetime: >>> from datetime import datetime >>> >>> dt = datetime.now() >>> dt.strftime('%Y.%m.%d %H:%I') '2020.01.08 12:00' Но мало кто знает, что тоже самое можно сделать и другим способом:…
Три способа создать декоратор для метода класса.
*▫️Способ 1*. Обычная функция.
Единственное отличие от простого декоратора функции в том, что нужно учитывать аргумент self
.
Если же он не нужен то просто пробрасываем его через *args
```
def decorator_func(func):
def wrapped(args, kwargs):
print('decorator_func')
return func(args, **kwargs)
return wrapped
class MyClass:
@decorator_func
def method(self):
print('call method')
MyClass().method()
# decorator_func
# call method
```
▫️Способ 2. Методы класса.
Но что, если декоратор жестко привязан к классу и используется только в нём. И стоит задача закрепить декоратор именно за этим классом и расположить внутри него.
В таком случае можно сделать staticmethod
. Это будет выглядеть страшно, но работать будет (тестировано на 3.11)
Очевидно, что декоратор должен быть объявлен раньше метода.
```
class MyClass:
@staticmethod
def decorator(func):
def wrapper(args, kwargs):
print('decorator from staticmethod')
return func(args, **kwargs)
return wrapper
@decorator.\_\_func\_\_
def method(self):
print('method called')
MyClass().method()
# decorator from staticmethod
# method called
```
Тоже самое будет и с classmethod
, но еще хуже.
```
class MyClass:
@classmethod
def decorator(func):
def wrapper(self, args, kwargs):
print('decorator from classmethod')
return func(self, args, **kwargs)
return wrapper
@decorator.\_\_func\_\_
def method(self):
print('method called')
MyClass().method()
# decorator from classmethod
# method called
```
Где-то потерялся аргумент cls
. Скорее всего это можно решить но лучше не надо. Оба варианта выглядят страшненько ?
▫️Способ 3. Вложенный класс и staticmethod
```
class MyClass:
class deco:
@staticmethod
def my_decorator(func):
def wrapper(args, kwargs):
print('decorator from subclass')
return func(args, **kwargs)
return wrapper
@deco.my\_decorator
def method(self):
print('method called')
MyClass().method()
# decorator from subclass
# method called
```
Получаем чтото вроде микса способов 1 и 2: функция вложена в отдельный класс.
Лучшей практикой является способ 1 - обычные функции.
Всего пару раз за практику я использовал 3й способ, когда декоратор был намертво привязан к классу и нигде больше не мог использоваться (например, отправлял вызов метода на воркера в другой процесс, не спрашивайте почему так, просто так было нужно ?)
Способ 2 не советую. Это, скорей, разминка для ума чем практический пример.
PS
- wraps
пропустил для краткости
- в коментах дополнительная инфа
Нередко требуется удалять дубликаты инстансов класса. Для этого обычно используется либо циклы со сравнением некоторых атрибутов, либо тип данных set()
.
При добавлении элемента в set
происходит сравнение этого объекта по хешу. Если хеш совпадает с хешем уже существующего объекта, то происходит сравнение объектов на равенство. Если объекты равны, то новый объект не добавляется.
```
class A:
def __init__(self, pk: int):
self.pk = pk
def __repr__(self):
return f"{self.__class__.__name__}(pk={self.pk})"
set([A(pk=1), A(pk=2), A(pk=2)])
{A(pk=1), A(pk=2), A(pk=2)}
```
Далее для краткости метод \_\_repr\_\_()
я буду пропускать
По умолчанию в расчёте хеша, помимо прочего, используется адрес в памяти, который можно получить с помощью функции id()
, поэтому все объекты считаются разными. Чтобы изменить способ сравнения объектов нам требуется переопределить метод __eq__()
```
class A:
def __init__(self, pk: int):
self.pk = pk
def __eq__(self, other):
return self.pk == other.pk
set([A(pk=1), A(pk=2), A(pk=2)])
TypeError: unhashable type: 'A'
```
Теперь в дело вступает логика, описаная в документации.
Если вы переопределили __eq__()
то следует переопределить и __hash__()
.
```
class A:
def __init__(self, pk: int):
self.pk = pk
def __eq__(self, other):
return self.pk == other.pk
def __hash__(self):
return hash(self.pk)
set([A(pk=1), A(pk=2), A(pk=2)])
{A(pk=1), A(pk=2)}
```
Отлично, теперь всё работает.
Этот же принцип действует и при наследовании. Допустим, вы создали дочерний класс
```
class B(A):
pass
set([B(pk=1), B(pk=2), B(pk=2)])
{B(pk=1), B(pk=2)}
```
Теперь следует учитывать вот такое поведение
```
hash(A(1)) == hash(B(1))
True
set([A(1), B(1)])
{A(pk=1)}
```
Инстансы А и В могут считаться идентичными, если они имеют одинаковые значения атрибутов и хеш, что может привести к неожиданным результатам при использовании множеств. Нужно учесть это в методах:
```
class A:
...
def __eq__(self, other):
return isinstance(other, self.__class__) and self.pk == other.pk
def \_\_hash\_\_(self):
return hash((self.pk, self.\_\_class\_\_))
...
```
Но если вдруг решите как-то изменить способ сравнения в классе В...
```
class B(A):
def __eq__(self, other):
return abs(self.pk) == abs(other.pk)
set([B(pk=1), B(pk=2), B(pk=2)])
TypeError: unhashable type: 'B'
```
Снова получите ошибку. Та же логика - при переопределении метода __eq__()
в новом классе метод __hash__()
автоматически становится None
и его тоже требуется переопределить.
Python documentation
3. Data model
Objects, values and types: Objects are Python’s abstraction for data. All data in a Python program is represented by objects or by relations between objects. (In a sense, and in conformance to Von ...
Библиотека APScheduler для управления заданиями в Python.
Может запускать планировщик и задания как отдельный поток (синхронный код) и как коркутины (асинхронный код), отложенные или через интервал.
Что есть в APScheduler:
▫️гибкий функционал создания задачи
▫️удобное управление созданными заданиями (pause\resume, listing, modify, reschedule)
▫️кастомизация классов библиотеки
▫️различные хранилища заданий (Memory и различные БД)
▫️интеграции в фреймворки
▫️7 вариантов планировщика
Три варианта тригеров для задач:
▫️по дате с помощью datetime
▫️через интервал с помощью datetime
▫️через интервал с помощью cron
и другие полезности
В данный момент готовится к релизу 4я версия
PS. Всегда использую вместе с FastAPI, очень рекомендую к ознакомлению.
GitHub
GitHub - agronholm/apscheduler at 3.x
Task scheduling library for Python. Contribute to agronholm/apscheduler development by creating an account on GitHub.
⭐️ Поздравляю!!! ⭐️
????♀️???
+ бонус в коментах ?**
Когда пишешь асинхронный код нужно учитывать особенности такого подхода. Всегда требуется держать в уме, когда возвращается корутина а когда реальный результат. Между этими двумя сущностями должен быть вызов через await
.
Вот пример синхронного запроса в базу данных с помощь sqlalchemy. Query пишу инлайном для компактности.
entities = session.execute(select(EntityModel)).scalars().all()
Всё ясно и линейно. А вот он же асинхронный.
result = await session.execute(select(EntityModel))
entities = result.scalars().all()
Это значит что session.execute
возвращает корутину, или awaitable объект. Сначала его нужно выполнить через await
, тогда получишь объект с которым можно дальше работать.
Не хочу сказать что это мастхэв практика, но простые асинхронные запросы тоже можно сократить до одной строки. Просто использовать скобки.
entities = ( await session.execute(select(EntityModel)) ).scalars().all()
На самом деле я использую такую конструкцию только в прототипах тестов или вспомогательных функциях тестов. В продакшн такое обычно не попадает.
PEP471 добавил в Python3.5 в модуль os
новую функцию scandir()
▫️это генератор с соответствующими возможностями
▫️возвращает не просто строку а объект DirEntry
▫️работает в 4-10 раз быстрей чем os.listdir
и os.walk
Раньше это была отдельная библиотека, которая позже стала частью CPython, как и ряд других новых библиотек в Python 3.
В настоящий момент метод Path.iterdir()
всё еще использует os.listdir()
.
Обёртка, заставляющая обычную функцию работать как генератор
def iterdir(self):
for name in os.listdir(self):
yield self.\_make\_child\_relpath(name)
В тоже время Path.glob()
и Path.rglob()
уже используют os.scandir()
, то есть полноценные генераторы.
peps.python.org
PEP 471 – os.scandir() function – a better and faster directory iterator | peps.python.org
Python Enhancement Proposals (PEPs)
Поздравляю всех с 2к24! ❄️⛄️???
Библиотеки для рабты с коллекциями файлов (секвенциями)
▫️ Поиск коллекций в директории
▫️ Проверка целостности
▫️ Поиск пересечений
▫️ Форматирование
И другие функции
*➡️ CLIQUE* https://clique.readthedocs.io/en/stable/
import clique
files = [
'/tmp/file1\_001.png',
'/tmp/file1\_002.png',
'/tmp/file1\_003.png',
'/tmp/file1\_005.png',
]
collection = clique.assemble(files)[0][0]
collection.head
\# '/tmp/file1\_'
collection.tail
\# '.png'
collection.padding
\# 3
collection.indexes
\# <SortedSet "[1, 2, 3, 5]">
collection.holes()
\# <Collection "/tmp/file1\_%03d.png [4]">
collection.separate()
\# [<Collection "/tmp/file1\_%03d.png [1\-3]">,
\# <Collection "/tmp/file1\_%03d.png [5]">]
➡️ PYSEQ https://pyseq.rsgalloway.com/
import pyseq
files = [
'/tmp/file1\_001.png',
'/tmp/file1\_002.png',
'/tmp/file1\_003.png',
'/tmp/file1\_005.png',
]
sequence = pyseq.Sequence(files)
sequence.head()
\# 'file1\_'
sequence.tail()
\# '.png'
sequence.path()
\# '/tmp/file1\_1\-5.png'
sequence.frames()
\# [1, 2, 3, 5]
sequence.format('%p')
\# '%03d'
sequence.missing()
\# [4]
У библиотек схожий функционал но в деталях различается.
clique
не умеет работать с pathlib.Path
а pyseq
не понимает генератор как источник. Но обе могут найти все коллекции в директории и выдать много информации о них.
Бизнес блог #1
Выжимаю книги до самой сути.
👉 Реклама - @jaMasha
📇 Хотите свою книгу? Мы напишем её за вас и сделаем книгу бестселлером. Подробности в боте @Summary_library_bot
🏆 Оставьте след в истории с помощью книги
https://expert-book.pro
Фильмы и сериалы со всей планеты. Мы знаем, что посмотреть, где посмотреть и на что сходить в кино.
Last updated 8 hours ago
Все материалы размещены по партнёрской програме ivi.ru | All materials are posted on the partner program ivi.ru
По всем вопросам: @kuzr103
Купить рекламу: https://telega.in/c/k1noxa103
Основной канал: https://t.me/kino_hd2
Last updated 1 month, 2 weeks ago