Zen of Python
18.8K subscribers
1.44K photos
202 videos
38 files
3.63K links
Полный Дзен Пайтона в одном канале

Разместить рекламу: @tproger_sales_bot

Правила общения: https://tprg.ru/rules

Другие каналы: @tproger_channels

Сайт: https://tprg.ru/site

Регистрация в перечне РКН: https://tprg.ru/xZOL
Download Telegram
Как читать многострочный ввод в Python REPL несколько раз

Вызов open(0).read() читает стандартный ввод до конца потока и сохраняет строки вместе с переносами. Для завершения ввода нажмите Ctrl+D в Unix или Ctrl+Z в Windows.

Повторный вызов заканчивается ошибкой OSError: после первого чтения Python закрывает файловый дескриптор 0. Передайте closefd=False, и стандартный ввод останется доступен: open(0, closefd=False).read() можно вызывать снова.

Явный параметр здесь действительно лучше неявно закрытого ввода. Приём пригодится, когда в интерактивной сессии нужно последовательно прочитать несколько блоков текста. Два таких чтения показаны в разборе TIL #139.
Как emojet ускоряет работу с эмодзи в Python

Библиотека emojet преобразует названия в эмодзи и обратно, поддерживает поиск и закрывает основное API пакета emoji. Имена доступны на 14 языках, а также в вариантах, принятых в GitHub и Slack.

В тестах автора на Python 3.14 для macOS и ARM импорт оказался в 23,7 раза быстрее, преобразование эмодзи в названия в 69,9 раза, обратная операция в 3,5 раза. Импорт с первым вызовом использовал около 17,4 МБ памяти вместо 29,2 МБ у emoji.

Раньше emoji при импорте загружал и разбирал JSON-файл размером 520 КБ. emojet написан на Rust и хранит данные в скомпилированных статических таблицах: разбирать файл при запуске не нужно. Для поиска имён используется хеш-таблица без коллизий, а для распознавания последовательностей эмодзи — дерево символов. В статье разобраны устройство библиотеки и воспроизводимый тест производительности.
Как запускать Python-код в песочнице на MicroPython и WebAssembly

В проектах Саймона Уиллисона плагины получают все права основного приложения: ошибка или вредоносный код могут повредить данные. Пакет micropython-wasm переносит интерпретатор MicroPython в WebAssembly и изолирует выполнение.

Память ограничивает wasmtime, а процессорное время контролирует «топливо»: запас операций, после исчерпания которого выполнение прекращается. Файлы, сеть и функции основного приложения доступны только через явно выданные интерфейсы. Состояние между вызовами сохраняет сессия с отдельным потоком и очередями запросов.

Пакет уже опубликован на PyPI, но пока имеет статус альфы: автор использует его сам, однако не рекомендует без готовности принять существенный риск. Архитектура, ограничения и команды для запуска разобраны в Simon Willison’s Weblog.
Почему Python предупреждает о последовательности \477 и как это исправить

В строках Python обратная косая черта и до трёх цифр от 0 до 7 задают восьмеричный код символа. Значение \477 равно 319 и выходит за предел 255, для которого такие последовательности задумывались. В обычной строке оно превращается в символ Ŀ, а в байтовой строке обрезается до 63 и даёт знак вопроса. Не самый явный способ записать путь.

В Python 3.11 предупреждение DeprecationWarning можно было увидеть в режиме разработки. Начиная с Python 3.12 компилятор выдаёт SyntaxWarning и без него; позднее конструкция должна стать синтаксической ошибкой.

Если косая черта в пути или регулярном выражении должна остаться символом, удвойте её: \\477. Если восьмеричный код был намеренным, укажите нужный символ явно. Механику и различие между str и bytes автор разбирает в статье Adam Johnson.
Как вручную разрешить ленивый импорт в Python

У объекта, созданного через lazy import json, есть метод resolve(). Его можно обнаружить с помощью dir(globals()["json"]), а справка поясняет: метод разрешает ленивый импорт и возвращает настоящий объект модуля.

Вызов globals()["json"].resolve() возвращает модуль json, но имя в глобальном пространстве по-прежнему указывает на объект lazy_import. Автор предполагает, что при автоматическом разрешении Python вызывает тот же метод, а затем присваивает имени полученный модуль.

В этот раз «явное лучше неявного» можно понимать буквально. В разборе показано, как исследовать этот механизм через dir() и help(); открытым остаётся вопрос, что именно запускает автоматическое разрешение импорта.
Почему str.splitlines() видит больше переносов, чем чтение файла

У Python два разных понятия границы строки. Универсальный режим чтения файла распознаёт LF, CR и CR LF. str.splitlines() шире: десять отдельных кодовых точек плюс CR LF.

Туда входят вертикальная табуляция, перевод страницы, разделители строки и абзаца Unicode и три ASCII-разделителя данных. Python собрал этот набор из правил ASCII, Unicode-переносов и двунаправленного текста. За простым методом стоят несколько наборов символов и пять документов Unicode.

История всех разделителей полезна при обработке внешнего текста: символ, задуманный как граница записи, может стать границей строки. Поэтому сначала сверяйте формат входа, а потом выбирайте чтение файла или splitlines().
😨1
Как собирать динамические фильтры Django через Q-объекты

Q() представляет условие для SQL-запроса. Такие объекты можно объединять операторами & и |, отрицать через ~ и передавать в один вызов filter(). Это позволяет явно задать, какие условия должны выполняться вместе, а какие служат альтернативами.

В примерах с Q-объектами поисковая строка разбивается на слова, для каждого строится проверка icontains, а затем условия соединяются через OR. Для небольших наборов данных этого достаточно, но на больших SQL-запросы с OR могут работать медленно.

Фильтры можно вынести в функции или методы моделей и собирать из пользовательских параметров. Автор предпочитает один filter(): так вся логика запроса видна в одном месте и проще контролировать JOIN при обходе связей. Явное условие лучше неявной цепочки, почти по PEP 20.
😘1
Как заменить цепочку isinstance на singledispatch

Когда обработка типов разрастается, цепочка isinstance() превращает одну функцию в дерево условий. Декоратор functools.singledispatch оставляет единый интерфейс, а реализации для отдельных типов регистрируются независимо. Выбор происходит по типу первого аргумента.

Учитывается и наследование: регистрация для Mapping подходит обычному dict через порядок разрешения методов. Обратная сторона той же механики: bool наследуется от int, поэтому для иной семантики ему нужен отдельный обработчик.

Аннотация помогает register() определить тип, но list[str] и list[int] не становятся разными случаями во время выполнения. В разборе singledispatch также показано, как тестировать обработчики отдельно, проверять выбор через dispatch() и проектировать явный fallback.
✍2
Как разделить синхронный и асинхронный Python-клиенты

При переписывании akismet автор отказался от одного класса с обычными и асинхронными методами. Клиент проверяет ключ API при создании, а для этого нужен HTTP-запрос. Синхронный запрос заблокировал бы асинхронный код, а запуск проверки через async потребовал бы заранее выбрать и получить работающий цикл событий.

Решением стали отдельные SyncClient и AsyncClient. Заодно новые имена позволили оставить прежний класс Akismet устаревшим, но рабочим, чтобы пользователи могли перейти без немедленной поломки API. Названия скучные, зато состояние клиента и способ ввода-вывода больше не приходится угадывать.

Разбор переписывания библиотеки также показывает, почему набор функций оказался хуже класса: ключ, адрес сайта и результат их проверки пришлось бы хранить в скрытом общем состоянии.
Как запускать собственный SQL через миграции Django

У models.Index нашлось необычное применение: его подкласс можно добавить в Meta.indexes, чтобы Django включил объект в автоматически созданную миграцию. При её выполнении фреймворк получит SQL из методов create_sql и remove_sql. Так к схеме таблицы можно привязать, например, комментарии или триггеры.

В django-security-label этот механизм задаёт правила анонимизации PostgreSQL прямо рядом с полями модели. Когда модель меняется, система миграций добавляет или удаляет соответствующие метки в схеме. Получается явно и рядом с предметной моделью, хотя автор прямо называет приём хаком.

Разбор механизма и пример с анонимизацией помогут оценить границы подхода. SQL из упрощённого примера копировать нельзя: он не экранирован должным образом.
Как проверять инварианты Python-кода с Hypothesis

Обычный тест фиксирует конкретный ввод и ожидаемый результат. Hypothesis предлагает описать свойство, которое должно выполняться для всех подходящих данных, а затем генерирует сотни вариантов. Явные инварианты лучше неявного каталога крайних случаев.

С @given и стратегиями можно проверять идемпотентность, сериализацию с обратным преобразованием и ограничения коллекций. Если свойство нарушено, Hypothesis упрощает вход до минимального контрпримера: вместо строки на тысячи символов остаётся значение, на котором удобно отлаживать код.

Практический разбор Hypothesis показывает стратегии генерации и интеграцию с pytest. Такие проверки дополняют обычные тесты, а найденную регрессию лучше закрепить отдельным тестом или @example.
👍1
Как коллизии превращают построение set в Python в квадратичную задачу

Привычное O(1) для set и dict предполагает, что коллизии редки. Если много ключей получают одинаковый хеш, интерпретатору приходится искать свободные ячейки и перебирать кандидатов при проверке вхождения. O(1) здесь полезная модель, а не договор с интерпретатором.

В эксперименте с подобранными целыми числами удвоение размера почти учетверяло время: построение множества из 16 000 элементов заняло 1072 мс, а из 100 000 — 45 секунд. Проверка всех элементов росла так же.

Отдельно автор измерил влияние процессорного кеша: поиск случайных строк в dict замедлялся по мере роста таблицы даже без коллизий. Это другой механизм, поэтому при неожиданной деградации стоит отдельно проверять распределение хешей и размер данных.
👍2🤩1
partial или lambda: как специализировать колбэк без сюрпризов

functools.partial заранее связывает часть аргументов и возвращает вызываемый объект. Сохранённые именованные аргументы работают как значения по умолчанию: при вызове их можно переопределить. Проверки новой сигнатуры при этом нет, поэтому повторная передача одного параметра закончится TypeError.

Разница с lambda проявляется во времени связывания. Лямбда получает текущее значение имени при выполнении, а partial сохраняет переданный объект при создании. Но копии не делает: изменения связанного списка будут видны, а долгоживущий колбэк удержит контекст в памяти.

Сравнение двух способов специализации функций сводится к питоничному правилу: если нужно только связать аргументы, подходит partial. Если адаптер проверяет данные, логирует или преобразует аргументы, яснее написать именованную функцию через def.
🫡1