Синтаксис f-string позволяет использовать в строке символ обратного слеша "
▫️ вынести этот символ за скобки
\" но не позволяет использовать его внутри фигурных скобок.>>> lines = ['line1','line2']
>>> print(f'ITEMS: \n {"\n".join(lines)}')
SyntaxError: f-string expression part cannot include a backslash
Решения:▫️ вынести этот символ за скобки
n = '\n'
print(f'ITEMS: \n{n.join(lines)}')
▫️ заменить его на другое представление, например создавать нужный символ с помощью функции chr()
print(f'ITEMS: \n{chr(10).join(lines)}')
Чтобы узнать код символа следует использовать функцию ord()
>>> ord('\n')
10
А с юникодом не выйдет, там тоже слеш>>> print(f'ITEMS: \n{"\u000a".join(lines)}')
SyntaxError: f-string expression part cannot include a backslash
#tricks👍11😱1
Метод строки
А вот что будет по умолчанию
По умолчанию в качестве разделителя используется любой пробельный символ, будь то табуляция или новая строка. Включая несколько таких символов идущих подряд. А также игнорируются пробельные символы по краям строки.
#tricks #basic
split() разделяет строку на несколько строк по указанному символу>>> "a_b_c".split('_')
['a', 'b', 'c']
Можно указать максимальное количество разделений>>> "a_b_c".split('_', 1)
['a', 'b_c']
Или резать с другой стороны с помощью rsplit() (right split)>>> "a_b_c".rsplit('_', 1)
['a_b', 'c']
А что будет если оставить аргументы пустыми?>>> "a_b_c".split()Получаем список с одним элементом, потому что по умолчанию используется пробельный символ.
['a_b_c']
>>> "a b c".split()То есть это равнозначно такому вызову?
['a', 'b', 'c']
>>> "a b c".split(" ")
['a', 'b', 'c']
Кажется да, но нет! Давайте попробуем добавить пробелов между буквами>>> "a b c".split(" ")
['a', '', '', 'b', '', '', 'c']
И вот картина уже не так предсказуема 😕А вот что будет по умолчанию
>>> "a b c".split()Всё снова красиво! 🤩
['a', 'b', 'c']
По умолчанию в качестве разделителя используется любой пробельный символ, будь то табуляция или новая строка. Включая несколько таких символов идущих подряд. А также игнорируются пробельные символы по краям строки.
>>> "a\t b\n c ".split()Аналогичный способ можно собрать с помощью регулярного выражения. Но пробелы по краям строки придется обрабатывать дополнительно.
['a', 'b', 'c']
>>> import reЗдесь тоже можно указать количество разделений
>>> re.split(r"\s+", ' a b c '.strip())
['a', 'b', 'c']
>>> re.split(r"\s+", 'a b c', 1)А что если мы хотим написать красиво, то есть
['a', 'b c']
split() без аргументов, но при этом указать количество разделений? В этом случае первым аргументом передаём None
>>> "a\n b c".split(None, 1)Данный метод не учитывает строки с пробелами, взятые в кавычки
['a', 'b c']
'a "b c" '.split()Но для таких случаев есть другие способы.
['a', '"b', 'c"']
#tricks #basic
Telegram
Python Заметки
В посте про правильное использование аргумента shell упоминалось что в некоторых случаях атрибуты следует отправлять списком а не строкой. Что делать, если команда приходит именно строкой? Как её преобразовать в список?
Ответ очевиден
>>> cmd_str = 'ls …
Ответ очевиден
>>> cmd_str = 'ls …
👍25😱1😢1
Как не передавать аргумент в функцию если она его не ждёт?
Как-то раз я делал модуль с функциями, которые вызывались как фоновые задачи. В основном они принимали чёткий список позиционных аргументов. И вот, в разгар разработки, пришла новая фича - в каждую такую функцию теперь передаётся Lock-объект. Он позволяет сделать выполнение этой функции синхронным на разных воркерах (как и положено любому локеру).
Но вот проблема, в новых функциях, где нужен локер, я его, конечно же, принимаю как аргумент. Но в старых функциях он не предусмотрен. Часто функции вообще без аргументов.
Какие варианты решения?
▫️ Добавить во всех функциях в аргументы
▫️Проверить, может ли функция принять аргумент с определённым именем. И если не может то не передавать. Это можно сделать с помощью стандартной функции
#tricks
Как-то раз я делал модуль с функциями, которые вызывались как фоновые задачи. В основном они принимали чёткий список позиционных аргументов. И вот, в разгар разработки, пришла новая фича - в каждую такую функцию теперь передаётся Lock-объект. Он позволяет сделать выполнение этой функции синхронным на разных воркерах (как и положено любому локеру).
Но вот проблема, в новых функциях, где нужен локер, я его, конечно же, принимаю как аргумент. Но в старых функциях он не предусмотрен. Часто функции вообще без аргументов.
Какие варианты решения?
▫️ Добавить во всех функциях в аргументы
**kwargs. Это решит все проблемы. Строго говоря, это надо было сделать сразу. Теперь таски не будут падать из-за неизвестного аргумента. И теперь следует не забывать добавлять **kwargs в новых функциях. Но что, если нет возможности изменять код? Тогда...▫️Проверить, может ли функция принять аргумент с определённым именем. И если не может то не передавать. Это можно сделать с помощью стандартной функции
inspect.signature
from inspect import signatureТеперь можно проверить, ожидает ли функция параметр с определённым именем
def func(x, y, z=True):
pass
sig = signature(func)
print(sig)
# <Signature (x, y, z=True)>
print(sig.parameters)
mappingproxy(OrderedDict([('x', <Parameter "x">), ('y', <Parameter "y">), ('z', <Parameter "z=True">)]))
print('lock' in sig.parameters)
# False
Финальный псевдокодfrom tasks import my_task, LockClassКонечно же, наличие этого имени не гарантирует что функция ожидает именно этот тип. Но это уже нюансы реализации 😼
from inspect import signature
task_kwargs = {}
lock = LockClass()
if 'lock' in signature(my_task).parameters:
task_kwargs['lock'] = lock
my_task(**task_kwargs)
#tricks
👍17🤔2🔥1
Как получить путь к файлу текущего класса если метод получения пути находится в родительском классе в другом файле?
Например, представим такую ситуацию:
Чтобы получить правильный путь нам следует:
▫️ получить имя модуля текущего класса
Например, представим такую ситуацию:
# module1.py ###Что покажет код:
class BaseCls:
@classmethod
def get_path(cls):
print(__file__)
# module2.py ###
from module1 import BaseCls
class MainCls(BaseCls):
pass
import module2Мы ожидаем что путь будет к файлу
module2.MainCls.get_path()
module2.py, но переменная __file__ объявлена внутри файла module1.py и поэтому будет указывать именно на него.Чтобы получить правильный путь нам следует:
▫️ получить имя модуля текущего класса
module_name = module2.MainCls.__module__▫️ найти этот модуль в списке импортированных модулей
mod = sys.modules[module_name]▫️ получить значение переменной
__file__
filepath = mod.
__file__
Вся эта процедура, причём для любого типа объекта, есть в функции inspect.getfile(). Так что наш метод должен выглядеть так:# module1.pyТеперь из вызов этого метода из класса
import inspect
class BaseCls:
@classmethod
def get_path(cls):
print(inspect.getfile(cls))
MainCls найдёт путь к файлу module2.py
#tricks👍6
У тех, кто часто работает в терминале, есть привычка вызова особо часто используемых команд. например
Вместо команды
И так уж вышло, что эта привычка невольно у меня включается и при работе в REPL. Для быстрого выхода я жму
В общем, настолько высосанную из пальца проблему надо еще поискать 😆, но я нашел для неё решение!
Как это работает?
▫️ динамически создаётся новый тип объекта с помощью конструкции
▫️ в этом методе вызывается команда
▫️ имя
▫️ Нужно как-то добавлять это в стартап. Тут нам поможет startup script
Аналогичным способом можно сделать и другие действия, но стоит помнить что это нестандартное поведение в Python, в прод не оставляйте!
Пару раз я вставлял аналогичные объекты в интерактивную консоль для дебага, они там выполняли роль шорткатов для каких-то наборов действий
PS. Не очень-то это похоже на трик или лайфхак. Это скорей демонстрация гибкости языка в решениинадуманных нестандартных проблем.
#tricks
cd, ls, mc...Вместо команды
exit можно использовать Ctrl+D, и это удобно. И так уж вышло, что эта привычка невольно у меня включается и при работе в REPL. Для быстрого выхода я жму
Ctrl+D, и это работает, но только в Linux. В Windows это совсем не работает, так как там надо нажимать Ctrl+Z. И был бы Windows не такой mustdie если бы этого хватило, но требуется еще нажать Enter (если знаете быстрый выход из REPL на винде, то подсказывайте, я не WinUser). Иногда мне быстрей и привычней вбить exit и нажать Enter, как в bash, но и тут подстава - еще нужны скобки вызова🤬.В общем, настолько высосанную из пальца проблему надо еще поискать 😆, но я нашел для неё решение!
q = type('q', (object,), {'__repr__': lambda *args: exit()})()
Этот код вставляется в стартап скрипт REPL и создаёт новый объект q. Теперь для выхода из REPL достаточно написать символ q и нажать Enter. Работает одинаково на Linux и Windows.Как это работает?
▫️ динамически создаётся новый тип объекта с помощью конструкции
type(NAME, (BASETYPES,), {ATTRS,})
▫️ в атрибутах создаётся оверрайд метода __repr__, который отвечает за распечатку объекта в REPL▫️ в этом методе вызывается команда
exit()
То есть команда выхода срабатывает как только вы пытаетесь распечатать этот объект в консоли. Именно отображение его репрезентации как объекта а не через не print(), который использует метод __str__.
Аналогично работающий код выглядит так:class Q:
def __repr__(self):
exit()
q = Q()
Из минусов можно выделить следующее:▫️ имя
q занято, но никто не мешает сделать что-то более уникальное▫️ Нужно как-то добавлять это в стартап. Тут нам поможет startup script
Аналогичным способом можно сделать и другие действия, но стоит помнить что это нестандартное поведение в Python, в прод не оставляйте!
Пару раз я вставлял аналогичные объекты в интерактивную консоль для дебага, они там выполняли роль шорткатов для каких-то наборов действий
PS. Не очень-то это похоже на трик или лайфхак. Это скорей демонстрация гибкости языка в решении
#tricks
Telegram
Python Заметки
Startup скрипт для REPL.
Как выполнить скрипт сразу после старта интерактивной консоли Python?
Для начала понять бы зачем это может понадобиться. А причины бывают достаточно весомые
- автоматизировать одни и те же действия которые вы повторяете при старте…
Как выполнить скрипт сразу после старта интерактивной консоли Python?
Для начала понять бы зачем это может понадобиться. А причины бывают достаточно весомые
- автоматизировать одни и те же действия которые вы повторяете при старте…
😁7👍3👎1
SQLAlchemy - это один из самых популярных ORM для работы с базами данных из Python.
- поддерживат все популярные базы данных
- не привязана к какому-либо фреймворку (как, например, Django ORM)
- поддерживает асинхрон
- позволяет удобно (питонично) делать довольно сложные SQL запросы
15 июля вышла первая версия из ветки 2.0 и это хорошйи повод изучить эту библиотеку если еще не начали.
Подобрал вам ресурсы для изучения:
- Вебинар и урок про новую SQLAlchemy2.0 от Mike Bayer (автор sqlalchemy и alembic ) с онлайн конференции pythonwebconf:
https://www.youtube.com/watch?v=Uym2DHnUEno
- Для тех кто на английском не очень, есть онлайн книга на руссом от https://t.iss.one/massonnn_yt.
А так же видео версия:
https://www.youtube.com/watch?v=leeC0fpAY-E&list=PLN0sMOjX-lm5Pz5EeX1rb3yilzMNT6qLM
Лично я использую алхимию в связке с FastAPI и пока всё устраивает
#tricks #libs
- поддерживат все популярные базы данных
- не привязана к какому-либо фреймворку (как, например, Django ORM)
- поддерживает асинхрон
- позволяет удобно (питонично) делать довольно сложные SQL запросы
15 июля вышла первая версия из ветки 2.0 и это хорошйи повод изучить эту библиотеку если еще не начали.
Подобрал вам ресурсы для изучения:
- Вебинар и урок про новую SQLAlchemy2.0 от Mike Bayer (автор sqlalchemy и alembic ) с онлайн конференции pythonwebconf:
https://www.youtube.com/watch?v=Uym2DHnUEno
- Для тех кто на английском не очень, есть онлайн книга на руссом от https://t.iss.one/massonnn_yt.
А так же видео версия:
https://www.youtube.com/watch?v=leeC0fpAY-E&list=PLN0sMOjX-lm5Pz5EeX1rb3yilzMNT6qLM
Лично я использую алхимию в связке с FastAPI и пока всё устраивает
#tricks #libs
www.sqlalchemy.org
The Database Toolkit for Python
👍22
Как проверить является ли директория пустой?
Самый простой способ:
Во втором случае мы получаем генератор, который под капотом использует тот же
Теперь представим что в директории 10к файлов
Для того чтобы ускорить проверку лучше воспользоваться функцией os.scandir(). Она работает на много быстрей и возвращает итератор с объектами os.DirEntry.
Чтобы узнать есть ли в директории хоть один файл достаточно использовать функцию
Самый простой способ:
if os.listdir(path):Тоже самое с
...
pathlib
p = Path(path)В первом случае функция
if list(p.iterdir()):
...
os.listdir возвращает полный список файлов. Нам остаётся проверить есть ли там что-либо.Во втором случае мы получаем генератор, который под капотом использует тот же
listdir.Теперь представим что в директории 10к файлов
for i in range(10000):Не сказать, что при наличии SSD это проблема, но когда таких операций много, мы начинаем терять время, особенно с
Path(f'/tmp/test/test{i}.txt').touch()
pathlib.import timeitТо есть мы получаем список всех 10к файлов просто чтобы узнать что там есть файлы. Хотя нам надо узнать есть ли по указанному пути хотя бы один файл.
test_path = '/tmp/test'
count = 1000
>>> timeit.timeit('list(os.listdir(p))', setup=f'import os;p="{test_path}"', number=count)
2.281363710993901
>>> timeit.timeit('list(p.iterdir())', setup=f'from pathlib import Path;p=Path("{test_path}")', number=count)
5.6957218300012755
Для того чтобы ускорить проверку лучше воспользоваться функцией os.scandir(). Она работает на много быстрей и возвращает итератор с объектами os.DirEntry.
Чтобы узнать есть ли в директории хоть один файл достаточно использовать функцию
next()
next(os.scandir(path))Но если директория пустая, то мы получим ошибку. Поэтому добавляем значение по умолчанию и можно использовать конструкцию в условном операторе
if next(os.scandir(path), None):Либо используем функцию
...
any(), так как она завершится сразу после нахождения первого файла или если итератор пуст.if any(os.scandir(path)):Сравним скорость
...
>>> timeit.timeit('next(os.scandir(p), None)', setup=f'import os;p="{test_path}"', number=count)
0.2183076049986994
>>> timeit.timeit('any(os.scandir(p))', setup=f'import os;p="{test_path}"', number=count)
0.21016486900043674
#tricksPython documentation
os — Miscellaneous operating system interfaces
Source code: Lib/os.py This module provides a portable way of using operating system dependent functionality. If you just want to read or write a file see open(), if you want to manipulate paths, s...
👍16❤4
Функция
Ранее я писал про функцию
Скорее всего вы уже знаете как использовать функцию
dir() - удобна для получения списка атрибутов у любого объекта.Ранее я писал про функцию
__dir__() в модуле (не путайте её с переменной __all__(), которая указывает список объектов для импорта если встречается конструкция from module import *). Скорее всего вы уже знаете как использовать функцию
dir(). Любой объект может реализовать метод __dir__() чтобы указать список имеющийхся и динамических атрибутов. И функция dir() поможет получить список этих атрибутов.>>> dir(str)У этой функции есть еще один способ применения. Её можно вызвать без аргумента, и в таком случае она вернёт список имён в текущем неймспейсе.
['__add__', '__class__', '__contains__', ...]
>>> dir()#basic #tricks
['__builtins__', '__doc__', '__file__', ...]
>>> def test():
>>> x = 1
>>> print(dir())
>>> test()
['x']
Telegram
Python Заметки
Знаете ли вы про "магические" методы классов ˍˍgetattributeˍˍ() и ˍˍgetattrˍˍ()?
ˍˍgetattributeˍˍ вызывается всякий раз когда идёт обращение к атрибуту объекта. Например метод или какая-то переменная.
ˍˍgetattrˍˍ вызывается когда атрибут не найден.
И…
ˍˍgetattributeˍˍ вызывается всякий раз когда идёт обращение к атрибуту объекта. Например метод или какая-то переменная.
ˍˍgetattrˍˍ вызывается когда атрибут не найден.
И…
👍7
Что позволяет делать f-strings в 3.12.
▫️можно использовать одинаковые кавычки во всём выражении
▫️можно добавлять переносы для многострочного выражения
▫️можно использовать символ новой строки (эта проблема неактуальна)
▫️можно использовать одинаковые кавычки во всём выражении
▫️можно добавлять переносы для многострочного выражения
▫️можно использовать символ новой строки (эта проблема неактуальна)
>>> print(f"{"\n".join(
>>> ["1","2","3",
>>> f"{
>>> f"{2+2}"
>>> *(2+2)
>>> }"
>>> ]
>>> )}")
1
2
3
4444
#tricks #libsPython documentation
2. Lexical analysis
A Python program is read by a parser. Input to the parser is a stream of tokens, generated by the lexical analyzer(also known as the tokenizer). This chapter describes how the lexical analyzer prod...
👍12
Варианты распаковки контейнеров по отдельным переменным
Обычная распаковка по точному количеству
Распаковка с неизвестным количество но не меньше чем N
Если точно знаете позицию нужного объекта в списке, включая вложенные списки, то достать его можно двумя способами
Через индекс:
Через распаковку со скобками:
Еще примеры распаковки вложенных объектов
#tricks
Обычная распаковка по точному количеству
data = [1, 2, 3, 4, 5]
v1, v2, v3, v4, v5 = data
Распаковка с неизвестным количество но не меньше чем N
v1, *_ = data
v1, *_, v4, v5 = data
Если точно знаете позицию нужного объекта в списке, включая вложенные списки, то достать его можно двумя способами
Через индекс:
data = [[1]]
v1 = data[0][0]
Через распаковку со скобками:
data = [[1]]
(v1, ), = data
data = [[[1]]]
((v1,), ), = data
Еще примеры распаковки вложенных объектов
data = [[1, 2], [3, 4], [5, 6]]
(v1, v2), (v3, v4), (v5, v6) = data
(v1, v2), *_, (v5, *_) = data
#tricks
🔥16👍6
Когда пишешь асинхронный код нужно учитывать особенности такого подхода. Всегда требуется держать в уме, когда возвращается корутина а когда реальный результат. Между этими двумя сущностями должен быть вызов через
Вот пример синхронного запроса в базу данных с помощь sqlalchemy. Query пишу инлайном для компактности.
Всё ясно и линейно. А вот он же асинхронный.
Это значит что
Не хочу сказать что это мастхэв практика, но простые асинхронные запросы тоже можно сократить до одной строки. Просто использовать скобки.
На самом деле я использую такую конструкцию только в прототипах тестов или вспомогательных функциях тестов. В продакшн такое обычно не попадает.
#tricks
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()
На самом деле я использую такую конструкцию только в прототипах тестов или вспомогательных функциях тестов. В продакшн такое обычно не попадает.
#tricks
👍9
Функция
Но вы не сможете таким образом получить аутпут процесса который завершился с ненулевым кодом выхода. Вместо этого у вас выбрасывается исключение
Не так давно я столкнулся с этой ситуацией, когда процесс, будучи запущенным с флагом
За генерацию исключения отвечает аргумент
Нет, это не недосмотр разрабочтков и вам не потребуется искать обходные пути. Дело в том, что вся полезная нагрузка в таких случаях находится в классе исключения.
Классы TimeoutExpired и CalledProcessError имеют ряд атрибутов, которые хранят всю нужну инфу. Например, вызванная команда (
Итого, базовая фукнция для захвата аутпута для любого кода выхода будет выглядеть как-то так:
#tricks
subprocess.check_output() удобна, когда нужно просто получить аутпут процесса.info = subprocess.check_output(cmd, text=True)
Но вы не сможете таким образом получить аутпут процесса который завершился с ненулевым кодом выхода. Вместо этого у вас выбрасывается исключение
CalledProcessError: Command '[...]' returned non-zero exit status 1.
Не так давно я столкнулся с этой ситуацией, когда процесс, будучи запущенным с флагом
--help, вполне штатно печатает в аут нужную информацию но выходит с кодом 1. И это для него нормальное поведение.За генерацию исключения отвечает аргумент
check, который по умолчанию равен False но именно в check_output он равен True и не может быть переопределён при вызове.Нет, это не недосмотр разрабочтков и вам не потребуется искать обходные пути. Дело в том, что вся полезная нагрузка в таких случаях находится в классе исключения.
Классы TimeoutExpired и CalledProcessError имеют ряд атрибутов, которые хранят всю нужну инфу. Например, вызванная команда (
cmd), код выхода (returncode) и то что мы ищем - аутпут процесса (output)Итого, базовая фукнция для захвата аутпута для любого кода выхода будет выглядеть как-то так:
def get_proc_output(cmd):
try:
return subprocess.check_output(cmd, text=True)
except subprocess.CalledProcessError as e:
return e.output
#tricks
Python documentation
subprocess — Subprocess management
Source code: Lib/subprocess.py The subprocess module allows you to spawn new processes, connect to their input/output/error pipes, and obtain their return codes. This module intends to replace seve...
👍13😁3🔥1
Нередко требуется удалять дубликаты инстансов класса. Для этого обычно используется либо циклы со сравнением некоторых атрибутов, либо тип данных
При добавлении элемента в
Далее для краткости метод `__repr__()` я буду пропускать
По умолчанию в расчёте хеша, помимо прочего, используется адрес в памяти, который можно получить с помощью функции
Теперь в дело вступает логика, описаная в документации.
Если вы переопределили
Отлично, теперь всё работает.
Этот же принцип действует и при наследовании. Допустим, вы создали дочерний класс
Теперь следует учитывать вот такое поведение
Инстансы А и В могут считаться идентичными, если они имеют одинаковые значения атрибутов и хеш, что может привести к неожиданным результатам при использовании множеств. Нужно учесть это в методах:
Но если вдруг решите как-то изменить способ сравнения в классе В...
Снова получите ошибку. Та же логика - при переопределении метода
#tricks
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 и его тоже требуется переопределить.#tricks
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. Even code is represented by objects. Ev...
👍7🔥1
Три способа создать декоратор для метода класса.
▫️Способ 1. Обычная функция.
Единственное отличие от простого декоратора функции в том, что нужно учитывать аргумент
Если же он не нужен то просто пробрасываем его через
▫️Способ 2. Методы класса.
Но что, если декоратор жестко привязан к классу и используется только в нём. И стоит задача закрепить декоратор именно за этим классом и расположить внутри него.
В таком случае можно сделать
Очевидно, что декоратор должен быть объявлен раньше метода.
Тоже самое будет и с
Где-то потерялся аргумент
▫️Способ 3. Вложенный класс и staticmethod
Получаем чтото вроде микса способов 1 и 2: функция вложена в отдельный класс.
Лучшей практикой является способ 1 - обычные функции.
Всего пару раз за практику я использовал 3й способ, когда декоратор был намертво привязан к классу и нигде больше не мог использоваться (например, отправлял вызов метода на воркера в другой процесс, не спрашивайте почему так, просто так было нужно 🤪)
Способ 2 не советую. Это, скорей, разминка для ума чем практический пример.
PS
-
- в коментах дополнительная инфа
#tricks
▫️Способ 1. Обычная функция.
Единственное отличие от простого декоратора функции в том, что нужно учитывать аргумент
self. Если же он не нужен то просто пробрасываем его через
*argsdef 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 пропустил для краткости- в коментах дополнительная инфа
#tricks
👍7
Объекты
Можно разделить один интервал на другой, включая целочисленное деление. Так мы узнаем сколько раз один период помещается в другой.
А так же остаток от делния.
Объекты
И, что очевидно, операторы сравнения
А еще можно почитать про форматирование даты и времени здесь и здесь.
#tricks
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
А еще можно почитать про форматирование даты и времени здесь и здесь.
#tricks
Telegram
Python Заметки
Все знают как красиво написать дату и время с помощью библиотеки datetime:
>>> from datetime import datetime
>>>
>>> dt = datetime.now()
>>> dt.strftime('%Y.%m.%d %H:%I')
'2020.01.08 12:00'
Но мало кто знает, что тоже самое можно сделать и другим способом:…
>>> from datetime import datetime
>>>
>>> dt = datetime.now()
>>> dt.strftime('%Y.%m.%d %H:%I')
'2020.01.08 12:00'
Но мало кто знает, что тоже самое можно сделать и другим способом:…
🔥14👍3