fa
Feedback
Коробка с питоном

Коробка с питоном

رفتن به کانال در Telegram

Заметки от Python-разработчика: сниппеты, обзоры пакетов, новости и другая полезная информация. Автор: @kiriharu

نمایش بیشتر
462
مشترکین
-124 ساعت
اطلاعاتی وجود ندارد7 روز
-530 روز
آرشیو پست ها
Сегодня у нас простенькая задачка, а то пятница, все отдыхать хотят, я понимаю. Есть следующий код:
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 #задачки

Да, у нас есть тесты. А толку? Супер-мега-гига экслюзивный доступ к записи моего доклада с прошедшего PiterPy. Организаторы разрешили выложить не дожидаясь следующего года. Получилось как всегда холиварно, не всё всем может понравиться, но как минимум советую присмотреться к библиотечкам и статьям/докладам из полезных ссылок. Там немного, но самое годное из того что знаю по этой теме. Куда кидать помидоры и звездочки вы знаете 🙂🙃 https://youtu.be/liECQCFGfkE

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. Из минусов хотел бы отметить то, что автоматическая установка зависимостей может привести к запуску нежелательного кода. Но решение банальное - перед тем как что-то запускать, проверяйте, что вы запускаете. #pep

GIL скорее всего уберут из 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 #библиотека

Релиз FastAPI 0.100.0🔥 Себастьян закончил обновление pydantic-а. Теперь можно насладиться звуком ржавчины и приростом скорос
Релиз FastAPI 0.100.0🔥 Себастьян закончил обновление pydantic-а. Теперь можно насладиться звуком ржавчины и приростом скорости при переходе на новую версию FastAPI. Чейнжлог совсем небольшой получился https://fastapi.tiangolo.com/release-notes/#01000 p.s. в ближайшее время доберусь до чейнжлога pydantic v2 и сделаю обзорный пост

Релиз FastAPI 0.100.0🔥 Себастьян закончил обновление pydantic-а. Теперь можно насладиться звуком ржавчины и приростом скорос
Релиз FastAPI 0.100.0🔥 Себастьян закончил обновление pydantic-а. Теперь можно насладиться звуком ржавчины и приростом скорости при переходе на новую версию FastAPI. Чейнжлог совсем небольшой получился https://fastapi.tiangolo.com/release-notes/#01000 p.s. в ближайшее время доберусь до чейнжлога pydantic v2 и сделаю обзорный пост