Коробка с питоном
Ir al canal en Telegram
Заметки от Python-разработчика: сниппеты, обзоры пакетов, новости и другая полезная информация. Автор: @kiriharu
Mostrar más462
Suscriptores
-124 horas
Sin datos7 días
-530 días
Archivo de publicaciones
Сегодня у нас простенькая задачка, а то пятница, все отдыхать хотят, я понимаю.
Есть следующий код:
def test():
try:
return 1
finally:
return 2
Вопрос - что вернется при вызове test()? Все и так на этом моменте понимают, что вернётся 2 (ну не просто так же мы собрались, верно?), но почему?
Ответ, как обычно, есть в документации. Возвращаемое функцией значение определяется последним выполненным return.
Вторым важным аспектом является то, что finally исполняется всегда, поэтому мы и получаем его return.
raise, кстати, тоже работать не будет:
def test():
try:
raise ValueError()
finally:
return 3
test() # 3
#std #задачкиРешил канал расширить ещё одной тематикой - занимательными задачками.
Пока что буду писать про те, которые встречались на тех. собеседованиях. Они не всегда будут адекватные, но что уж есть :)
А начнём, как полагается с классики. Надо объяснить следующее поведение:
>>> a = 256
>>> b = 256
>>> a is b
True # ???
>>> a = 257
>>> b = 257
>>> a is b
False # ???
Вопрос в том, что здесь творится с ссылками. Разберём самую первую часть. Пробуем получить id объектов:
>>> a = 256
>>> b = 256
>>> id(a), id(b)
(2214170730704, 2214170730704)
На вопрос, почему у них одинаковые идентификаторы ответит деталь реализации PyLong_FromLong (для искушенных читать можно читать отсюда), которая указывает, что интерпретатор хранит массив целочисленных объектов для всех чисел в диапазоне от -5 до 256. Поэтому, когда мы создаем переменную с числом в этом диапазоне он просто отдает ссылку на уже существующий объект.
Микрооптимизация, при чём очень важная - так уж получилось что числа из этого диапазона используются чаще всего.
В Java есть похожая оптимизация, там такой диапазон составляет от -128 до 127, но есть нюансы.
Второй вопрос отпадает сам собой (будут разные ссылки), но что будет если мы создадим файл с следующим содержимым и запустим его:
a = 257
b = 257
print(a is b) # True
А вот это уже нюанс работы нашего REPL.
Каждая написанная нами строка в нём разбирается отдельно. Но при запуске через файл Python имеет возможность применить дополнительные оптимизации для констант, так как он видит сразу весь код - в этом и различие.
#std #задачкиRepost from Николай Хитров | Блог
Да, у нас есть тесты. А толку?
Супер-мега-гига экслюзивный доступ к записи моего доклада с прошедшего PiterPy. Организаторы разрешили выложить не дожидаясь следующего года. Получилось как всегда холиварно, не всё всем может понравиться, но как минимум советую присмотреться к библиотечкам и статьям/докладам из полезных ссылок. Там немного, но самое годное из того что знаю по этой теме. Куда кидать помидоры и звездочки вы знаете 🙂🙃
https://youtu.be/liECQCFGfkE
Repost from Питонические атаки
PSF и JetBrains запустили своё ежегодное исследование Python Developers Survey 2023. Погнали заполнять! 👇
https://survey.alchemer.com/s3/7554174/python-developers-survey-2023
Через год, когда подведут результаты, будет повод написать пост, типа такого 😅
В одном чатике встретил забавный проект fakeredis, реализующий Redis-сервер на питоне, который можно использовать для тестов. Очевидно, чтобы не таскать для тестов настоящий Redis.
Поддерживаются практически все стандартные команды, которые я встречал для работы с коллекциями (список здесь). Есть не полная поддержка JSON, поиска, time series.
Если каких-то команд не хватает, автор оставил гайд, как её сделать. Ну а если сделаете - законтрибутьте в опенсорс, полезное дело всё таки!
#библиотека
На сегодня расскажу ещё пару рецептов с
more_itertools.
1) map_if работает как обычный map, но применяет функцию на элемент только если оно попадает под условие. Например, вот так мы можем возвести в квадрат только те числа, которые делятся на 2 нацело:
example = [1, 2, 3, 4, 5, 6, 7, 8]
list(map_if(example, lambda x: x % 2 == 0, lambda x: x * x)) # [1, 4, 3, 16, 5, 36, 7, 64]
2) Получить последний элемент можно при помощи last. Возникает вопрос а зачем он существует, если можно указать iterable[-1]? Ответом является то, что last позволяет указать, что ему возвращать, если элементов в итерируемом объекте нет:
last([1, 2, 3]) # Очевидно получим 3
last([], 0) # Список пустой, но получим 0
[][-1] # Получим IndexError
Ещё есть first - как понятно из названия, он получает первый элемент.
3) map_except тоже работает как map, но умеет игнорировать ошибки. Например, мы хотим получить только те элементы, которые получилось привести к целому числу:
example = [1, "1", "2", "test", "three", object, 4.0]
list(map_except(int, example, ValueError, TypeError)) # [1, 1, 2, 4]
4) Ну и в конце про take - он просто берет N элементов из итерируемого объекта:
example = range(10)
take(3, example) # [0, 1, 2]
take(20, example) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - если больше, возьмет доступные
#itertools #more_itertools #библиотекаПока я спал, руководящий совет языка принял PEP 703 (nogil).
Кратко о том, о чём говорится в посте:
1. Руководящему совету ясно, что несмотря на все проблемы и недостатки потоков, nogil будет полезен для Python, так как позволит находить более масштабируемые решения.
2. В то же время, они не уверены, получится ли убрать GIL не сломав при этом обратную совместимость - всё же не хотелось бы терять десятилетия развития базы пакетов. Существующая пакетная экосистема - это одна из сильных сторон языка, как и простая интеграция библиотек на C c CPython.
3. Оценить влияние nogil без реализации сложно, поэтому nogil должен выпускаться в составе регулярных релизов и не обязательно он там должен быть по-умолчанию.
4. Это всё ещё не гарантированная история. Если что-то пойдет не так - от изменений откажутся. Развёртывание должно быть постепенным и наиболее плавным.
5. Выкатка будет происходить в 3 фазы, которые возможно изменятся:
- В первой фазе nogil сделают возможным таргетом при сборке, чтобы разработчики могли тестировать свои пакеты.
- Во второй фазе, когда изменения в API и ABI будут сформированы, а поддержка nogil от сообщества будет достаточной, nogil-сборку добавлят как "поддерживаемую, но не по умолчанию".
- В третьей фазе nogil-сборку сделают сборкой "по-умолчанию", а от gil-сборки будут отказываться.
6. При успешной реализации nogil, ожидается падение производительности на 10-15% в худшем случае.
Недавно пришлось писать конвертор который транслировал Markdown в довольно специфичный формат.
Была следующая идея - мне нужен парсер, который перегонит Markdown в синтаксическое дерево, которое я буду обходить.
С обычным python-markdown в этом плане банально неудобно работать - у него нет простого и расширяемого API, который бы позволял решить мою задачу.
А вот у mistletoe такое API есть. Например, вот так выглядит рендерер markdown в jira:
class JiraRenderer(BaseRenderer):
def render_strong(self, token):
template = '*{}*'
return template.format(self.render_inner(token))
def render_emphasis(self, token):
template = '_{}_'
return template.format(self.render_inner(token))
Ещё mistletoe можно использовать как утилиту. Например, вот так markdown-файл можно перевести в тот же формат Jira:
mistletoe foo.md --renderer mistletoe.contrib.jira_renderer.JiraRendererПоэтому, если вам нужна небольшая, но расширяемая библиотека для работы с markdown - берите её, не прогадаете. #библиотека
На второе октября намечен релиз Python 3.12, поэтому Никита Соболев (opensource разработчик и контрибьютор в сpython) рассказывает про новинки в новой версии и чуть затрагивает то, что ожидает нас в 3.13.
Рекомендую посмотреть, чтобы оставаться в теме новых обновлений.
#посмотреть
Помните, я рассказывал про такой фреимворк как Propan? В общем, программа Propan удаляется на смену Propan пришел FastStream, и весь новый функционал будет попадать в него.
Первой моей реакцией было "што" и "зачем", ведь на первый взгляд может показаться, что поменялись только орга и название. Давайте разбираться.
Во-первых, за фреимворком стоит не только сам Lancetnik, а целая команда из AirtAI, что является очень сильным усилением со стороны как разработки, так и поддержки.
Второе, FastKafka переходит в такой же статус как и Propan. Всё по той же причине - новый функционал будут добавлять в FastStream.
В свою очередь FastStream - это фреимворк, базированный на идеях FastKafka и Propan, который хочет взять от них самое лучшее и создать единый способ для написания сервисов, работающих с очередями.
Третье, что показалось мне важным, разработчик Propan признаётся, что сделать унифицированный RPC для всех брокеров (например для Kafka) - задача сложная, особенно когда брокер by design плохо рассчитан на такую функциональность. Всё сводилось к тому, что тот самый простой высокоуровневый интерфейс плохо скрывал недостатки реализации, поэтому в FastStream от него отказались.
Ещё генерацию темплейтов удалили, теперь проект можно генерировать при помощи cookiecutter.
А что нового уже принёс FastStream?
В первую очередь, мне понравился механизм фильтров. Ещё добавили кастомную сериализацию - по умолчанию FastStream работает с Json, но если у вас Protobuf, Avro или msgpack - больше никаких проблем нет, можно добавить собственную логику для сериализации.
Отдельно хотел бы выделить систему мидлварей - теперь трассировку, логи, обогащение сообщений метаинформацией делать намного проще.
Очень надеюсь, что проект будет развиваться семимильными шагами, ведь идеи FastKafka и Propan мне очень сильно понравились, а сам Propan я очень часто рекомендовал знакомым.
#библиотека
Если вам приходилось работать с bash, например парсить логи, вы наверняка видели или использовали команды следующего вида:
cat app.log | grep 'exception' | awk '{ print $2 }'
Здесь мы последовательно считываем содержимое файла app.log, фильтруем по подстроке exception и выводим вторую колонку.
Символ |, которая разделяет команды называется "пайпом" - он перенаправляет вывод из одной команды в другую, а вся эта конструкция в целом называется конвейером, и она очень часто используется в функциональном программировании.
В Elixir есть оператор конвейера |> который позволяет передавать результат выполнения левого выражения в правое. Это позволяет делать вот такие штуки:
1..10 |> Enum.map(fn x -> x * 2 end) |> Enum.filter(fn x -> x > 10 end) [12, 14, 16, 18, 20]Здесь мы генерируем список от 1 до 10, умножаем каждый элемент на 2 и фильтруем только те, которые больше 10. Согласитесь, так удобнее работать с коллекциями, особенно там где данных много и их хочется как-то отфильтровать - нам больше не надо строить громоздкие циклы. Если вам интересно. как можно сделать такое в Python, то для вас есть библиотека Pipe. Реализация очень простая - библиотека добавляет класс Pipe который переопределяет оператор
|. Далее мы оборачиваем наши функции в этот класс и можем строить конвейеры!
Например, вот так мы можем получить сумму товаров, цена которых больше тысячи:
from typing import NamedTuple
from pipe import select, where
class Product(NamedTuple):
name: str
price: int
products = [Product("choco", 123), Product("auto", 10000), Product("photo", 1200)]
sum(
products
| select(lambda product: product.price)
| where(lambda x: x > 1000)
) # 11200
#библиотекаНедавно мой знакомый @nesclass (можете ему писать по теме доклада) выступал c докладом "Асинхронное варение MongoDB в Python" на Pytup.
Автор объяснил наглядно в чем различие реляционных баз от документно-ориентированных, немного рассказал про подходы работы с БД, затронул тему object mapper'ов и много рассказал про Beanie - ODM для работы с MongoDB (про которую я писал) и саму монгу.
Доклад отлично подойдет для тех, кто хочет ознакомиться с этим инструментом и послушать про подводные камни, с которыми часто встречаются новички.
Тианголо (создатель FastAPI) предложил новый PEP 727, который позволяет стандартизировать механизм документирования параметров.
Причина проста - существует много псевдо-стандартов по форматированию параметров в docstring (например свои стандарты есть у numpy, гугла, много их!), но не все редакторы/IDE имеют возможность поддерживать эти "микроязыки". Он предлагает стандартизировать подход, используя существующий мощный инструмент в виде аннотаций типов.
Основное предложение - добавление в
typing новой функции doc, которая принимает единственный параметр documentation. Ожидается, что эта функция будет использоваться вместе с Annotated.
А вот как это будет выглядеть:
def create_user(
lastname: Annotated[str, doc("The **last name** of the newly created user")],
firstname: Annotated[str | None, doc("The user's **first name**")] = None,
) -> Annotated[User, doc("The created user after saving in the database")]:
"""
Create a new user in the system, it needs the database connection to be already
initialized.
"""
pass
Тианголо заранее позаботился и о старых версиях - так же предлагается добавить doc() в пакет typing_extensions, который используют старые версии питона для исключения проблем с совместимостью.
Выглядит интересно, но важно понимать - автор пытается создать ещё один стандарт. Поддержит ли его комьюнити и уйдет ли PEP в работу? Остается только гадать.
#pepНемного девопсовский пост, так как на предыдущем месте работы я много админил линукса.
Автоматизация - один из столпов работы любого админа, так как подготавливать сервера руками - занятие тяжелое и трудозатратное.
Для этого я всегда выбирал Ansible - это довольно простой инструмент, который позволяет настраивать сервера из специальных yaml конфигов, которые называются плейбуками.
Но какой бы вы инструмент не выбрали, рано или поздно конфигурация будет усложняться, а плейбуков/скриптов станет много. В какой-то момент всё это может сломаться, поэтому любую автоматизацию надо тестировать.
Когда я админил, команда не хотела полностью завязываться на Ansible (соответственно Molecule отпадает), решено было искать инструмент, который позволял бы тестировать состояние сервера после работы любой автоматизации.
И я нашел такой инструмент, имя ему - testinfra. Это плагин для pytest, который позволяет лаконично описывать тесты проверяющие состояние сервера.
Например, мы написали автоматизацию, которая устанавливает nginx. Проверим, что он установлен:
def test_nginx_is_installed(host):
nginx = host.package("nginx")
assert nginx.is_installed
А теперь проверим, что сервис с ним запущен:
def test_nginx_running_and_enabled(host):
nginx = host.service("nginx")
assert nginx.is_running
assert nginx.is_enabled
Самое важное - для него существует много connection backend'ов, которые позволяют подключаться к серверам для теста как по SSH (через paramiko), так и используя инвентарь Ansible. Так же есть бекенды для docker/podman/salt/kubectl/openshift/winrm/lxc - обо всём этом можно почитать здесь.
Напоследок, вот вам еще кейс использования: одно время при помощи testinfra я тестировал правильность сборки контейнеров - все ли есть права, запустился ли сервис, в порядке ли конфиги и так далее.
#devopsЗанимаюсь в рамках петпроекта обработкой текста, появилась потребность проверять на каком языке написан текст.
Сначала я попытался использовать langdetect, но он часто выдавал неправильные результаты. Как правило, плохие результаты выдавались по нескольким причинам:
1. Нет возможности ограничить языки, которые я хотел бы детектить. Мне надо определять всего четыре языка - украинский, русский, английский и немецкий.
2. Часто исследуемый мною текст слишком мал, из-за чего анализ ломался.
По итогу я пошел искать другую библиотеку и нашел lingua, которая успешно справляется с проблемами langdetect.
Важное отличие этой библиотеки от всех остальных в том, что она использует не только статистическую модель для определения языка, а ещё и механизмы, основанные на правилах - сначала определяется алфавит текста, ищутся символы которые уникальны для языка и после этого выбираются языки, на которых возможно написан текст.
Но есть возможность улучшить этот процесс. Можно самому ограничить языки, на которых возможно будет написан текст, а это как раз то, что мне и нужно:
from lingua import Language, LanguageDetectorBuilder
languages = [Language.ENGLISH, Language.RUSSIAN, Language.GERMAN, Language.UKRAINIAN]
detector = LanguageDetectorBuilder.from_languages(*languages).build()
print(detector.detect_language_of("Hello from box with python!")) # Language.ENGLISH
print(detector.detect_language_of("Привет от коробки с питоном!")) # Language.RUSSIAN
Из-за ограничения языков вероятность совершить ошибку на небольших предложениях сокращается многократно. В добавок ко всему, ребята используют не только триграммы, которые очень часто используют для таких задач, а n-граммы от 1 до 5, из-за чего вероятность предсказания повышается.
Причина такого решения проста - чем короче входной текст, тем меньше n-грамм доступно, а если мы будем проверять триграммами короткие заголовки - будут случаться ошибки.
Ну и база - ограничение количества языков ускоряет работу и уменьшает потребление памяти, а при исследовании огромных текстов - это несомненный плюс.
#nlp #библиотекаПоявился PEP 723, который предлагает "встраивать"
pyproject.toml в однофайловые скрипты.
Предлагается добавить переменную __pyproject__, которая будет содержать в себе валидный TOML, описывающий метадату скрипта, в том числе как скрипт запускать и какие зависимости необходимы для запуска.
К примеру, вот так будет выглядеть скрипт, которому для работы нужна библиотека requests и питон 3.11 или выше:
__pyproject__ = """
[project]
requires-python = ">=3.11"
dependencies = [
"requests<3",
]
"""
import requests
resp = requests.get("https://peps.python.org/api/peps.json")
print(resp.json())
PEP прикольный, что-то такое есть в качестве экспериментального RFC в Rust. Из минусов хотел бы отметить то, что автоматическая установка зависимостей может привести к запуску нежелательного кода. Но решение банальное - перед тем как что-то запускать, проверяйте, что вы запускаете.
#pepGIL скорее всего уберут из Python!
Руководящий совет объяснил статус PEP 703 (сделать GIL опциональным). Если кратко, то понятно следующее:
— Они намерены принять PEP 703, но всё ещё работают над деталями, предстоит много работы.
— Вероятно, лет через пять сборка без GIL будет ЕДИНСТВЕННЫЙ сборкой, так как они не хотят разделять комьюнити на no-GIL и GIL.
— Совет не хочет повторения ситуации с Python 3, поэтому большое внимание будет уделено обратной совместимости.
— Прежде чем отказаться от GIL, они внимательно будут изучать работу комьюнити в этом направлении. Ребята хотят убедиться, что переход будет плавным, а поддержка режима no-GIL будет достаточна.
— Не смотря на все это, совет хочет иметь возможность "дать заднюю", если для комьюнити и языка это принесёт намного больше проблем, чем профита.
Новость просто невероятная. Остается надеяться на комьюнити, будет ли оно готово адаптировать свои библиотеки под этот режим?
#gil
Yoyo
В текущих реалиях практически любой сервис использующий реляционную БД не может обойтись без миграций. Так уж сложилось, что в нашем питонячьем мире, в проектах почти всегда можно увидеть ORM (например SQLAlchemy) и пакет, который позволяет управлять миграциями, работающий с конкретной ORM (в случае с Алхимией это alembic).
Но иногда попадаются такие проекты, в которых нам не нужна ORM, по разным причинам. В этом случае, для миграций можно использовать yoyo.
Yoyo - это простой мигратор схем для нашей БД. Миграции здесь записываются в виде скриптов на SQL или на Python.
Сгенерировать папку для миграций и конфиг можно вот так:
yoyo init --database sqlite:///mydb.sqlite3 migrationsА теперь создадим миграцию:
yoyo newОпишем SQL для применения миграции (apply) и для её отката (rollback):
from yoyo import step
__depends__ = {}
steps = [
step(
"CREATE TABLE TEST (a int);",
"DROP TABLE TEST"
)
]
Теперь можно применить миграцию командой yoyo apply и после откатить её командой yoyo rollback.
Внутри базы yoyo создает таблицу с логом всех операций (_yoyo_log), таблицу с применёнными миграциями (_yoyo_migration), а так же таблицу yoyo_lock который позволяет заблокировать миграции, которые будут идти с нескольких инстансов приложений.
Вот такая вот простая и удобная библиотека :)
Сайт | Sourcehut | PyPI
#библиотекаRepost from Николай Хитров | Блог
Релиз
FastAPI 0.100.0🔥
Себастьян закончил обновление pydantic-а. Теперь можно насладиться звуком ржавчины и приростом скорости при переходе на новую версию FastAPI.
Чейнжлог совсем небольшой получился
https://fastapi.tiangolo.com/release-notes/#01000
p.s. в ближайшее время доберусь до чейнжлога pydantic v2 и сделаю обзорный постRepost from Николай Хитров | Блог
Релиз
FastAPI 0.100.0🔥
Себастьян закончил обновление pydantic-а. Теперь можно насладиться звуком ржавчины и приростом скорости при переходе на новую версию FastAPI.
Чейнжлог совсем небольшой получился
https://fastapi.tiangolo.com/release-notes/#01000
p.s. в ближайшее время доберусь до чейнжлога pydantic v2 и сделаю обзорный пост