zen_of_python | Unsorted

Telegram-канал zen_of_python - Zen of Python

19037

Полный Дзен Пайтона в одном канале Разместить рекламу: @tproger_sales_bot Правила общения: https://tprg.ru/rules Другие каналы: @tproger_channels Сайт: https://tprg.ru/site Регистрация в перечне РКН: https://tprg.ru/xZOL

Subscribe to a channel

Zen of Python

Дэвид Бизли пишет с нуля живьём на сцене, за 46 минут проходя весь путь от простого сокета до собственного цикла событий. Слайдов почти нет, только редактор и терминал.

Последовательность такая. Берётся заведомо тяжёлая функция — рекурсивные числа Фибоначчи — и простой сетевой сервис. Сначала он последовательный, и один клиент блокирует всех остальных. Дальше появляются потоки: они спасают там, где программа ждёт ввод-вывод, но на вычислениях упираются в глобальную блокировку интерпретатора. Затем процессный пул, который блокировку обходит, но платит за это сериализацией и передачей данных.

Самая ценная часть в конце. Бизли показывает, что генератор с yield — это способ задачи добровольно приостановиться, и из этого прямо на глазах собирает планировщик с очередью задач, а потом цикл событий с select, ожиданием и пробуждением через пару сокетов.

После этого asyncio перестаёт быть магией: видно, из каких частей он состоит и почему устроен именно так.

@zen_of_python

Читать полностью…

Zen of Python

uv 0.12.5 поменял правило выбора интерпретатора: при одинаковом приоритете теперь предпочитается более новая версия и стандартный вариант Python. Если в конфигурации несколько интерпретаторов с равным приоритетом, окружение может собраться уже не тем Python, что вчера.

🔘 подтянуты CPython 3.10.21, 3.11.16 и 3.12.14;
🔘 в preview у --index и --default-index появился выбор настроенного индекса по имени;
🔘 экспорт SBOM в CycloneDX по умолчанию содержит URL и хеши артефактов;
🔘 cache-physical-space откатывается к логическому размеру файла там, где физический размер файловая система не отдаёт;
🔘 в скриптах по PEP 723 относительные пути к индексам разрешаются относительно каталога скрипта, а не текущего каталога.

Выбор индекса по имени авторы помечают как preview. Численных бенчмарков в заметках к релизу нет.

@zen_of_python

Читать полностью…

Zen of Python

Numba 0.67.0 научилась работать с NumPy 2.5 и собирает пакеты для Python 3.14 и Windows ARM64.

🔘 у np.sum и np.cumsum появился динамический axis, ось теперь можно вычислять во время выполнения;
🔘 добавлена JIT-поддержка np.insert, пока без аргумента axis;
🔘 np.random.binomial в RandomState переключается с BINV на BTPE, когда n * min(p, 1 - p) > 30, поток случайных чисел при этом сохраняется;
🔘 анализ живости переменных перевели на топологический порядок, а проверка принадлежности стеку в _find_back_edges стала за O(1);
🔘 сборки через pycc теперь могут быть побайтно воспроизводимыми на Linux и macOS.

Из ломающего: np.row_stack и двумерное векторное произведение убраны вслед за NumPy 2.5, cross2d остаётся только для старых версий. Пакеты под Windows ARM64 на старте ограничены Python 3.14, часть тестов там пропущена из-за проблемы в LLVM 22.

Отдельно починили тихую ошибку компиляции при повторном присваивании нелокальной переменной во вложенной функции: раньше генерировался неверный код, теперь вылетает UnsupportedError. Численного бенчмарка ускорения BTPE авторы не приводят.

@zen_of_python

Читать полностью…

Zen of Python

⚡️ О!Хакатон возвращается — на кону миллион

Приглашаем Go- и Python-разработчиков, аналитиков и продакт-менеджеров на тревел-тех хакатон от Островка.

Вам предстоит решить одну из двух AI-задач в сфере тревел-теха и побороться за главный приз — 1 000 000 ₽.
Присоединиться к хакатону можно из любой точки мира. Участвуйте командой до пяти человек и выбирайте один из двух треков:

✔️ «О!дин запрос». Цель для junior-специалистов — создать AI-агента для путешествий и отелей.
✔️ «О!дин шаг до идеального отеля». Задача для специалистов уровней middle и senior — «переизобрести» опыт бронирования пользователя с помощью AI и новых интерфейсных механик.

📎 Регистрация открыта до 16 октября 2026 года. Стартуем 23 октября.

Скорее присоединяйтесь по ссылке!

Читать полностью…

Zen of Python

Django переходит на один релиз в год. Steering Council принял DEP 20: с января 2028-го выходит один feature-релиз в год, версии называются по году — Django 2028, Django 2029.

🔘 каждый релиз получает три года поддержки: год обычных исправлений и два года security и data-loss fixes; отдельная метка LTS уходит, потому что каждая версия теперь LTS;
🔘 версия поддерживает три последних Python на момент выхода и подхватывает новый Python в первый год; поддержка Django заканчивается вместе с самым старым из них;
🔘 в любой момент поддерживаются три версии, и обновляться можно по одной в год без прыжка через два года изменений;
🔘 политика депрекаций не меняется, в календарных днях сроки только удлиняются.

Переход: Django 6.1 вышел в августе, 6.2 LTS выйдет в апреле 2027-го, Django 2028 — в январе 2028-го. Обязательства по 5.2 LTS и 6.2 LTS остаются как объявлены.

@zen_of_python

Читать полностью…

Zen of Python

Разрешение зависимостей предложили считать задачей для решателя, а не перебором. SMTpip переводит ограничения пакетов и Requires-Python в формулу и отдаёт её weighted Max-SMT, который за один заход выбирает и версии библиотек, и версию интерпретатора. pip вместо этого перебирает кандидатов с откатами.

Что получилось на замерах авторов:

🔘 в среднем по всем наборам ускорение в 6,9 раза против pip, в 9,6 против классического решателя Conda, в 3,2 против smartPip и в 4 против PyEGo;
🔘 на наборе HG2.9K те же 1668 случаев: 433,68 секунды против 2165,20 у pip, ускорение в 4,9 раза;
🔘 доля проектов, которые после установки реально запустились: 87,1% против 75,4% у pip, а на 3081 ноутбуке 39,92% против 20%.

Оговорки авторы приводят сами. Это препринт первой версии; время меряли только для разрешения зависимостей, без скачивания и установки пакетов; из конкурентов брали классический solver Conda, libmamba оставили за рамками. Успешный запуск здесь означает, что программа стартовала, а не что она работает правильно.

@zen_of_python

Читать полностью…

Zen of Python

12 августа вышли security-релизы Python 3.12.14, 3.11.16 и 3.10.21. Список исправлений длинный, и почти всё в нём про стандартную библиотеку:

🔘 tarfile: несколько обходов фильтра data_filter(), через которые архив мог положить symlink за пределы каталога назначения; один из них — обход прошлогоднего CVE-2025-4330;
🔘 webbrowser.open() отклоняет URL с ведущим дефисом, чтобы аргумент не читался браузером как флаг; закрыт и обход этой проверки через префикс %action;
🔘 HTTPConnection.set_tunnel() больше не пропускает CR/LF в заголовках, wsgiref — управляющие символы в статусе, http.cookies — в Morsel (CVE-2026-3644);
🔘 SourcelessFileLoader открывает .pyc через io.open_code() (CVE-2026-2297), в xml.parsers.expat починен крэш от глубокой вложенности (CVE-2026-4224);
🔘 квадратичное и экспоненциальное поведение убрано из unicodedata.normalize(), html.parser, configparser, ElementTree и csv.Sniffer;
🔘 http.client ограничил число trailer-строк и промежуточных ответов 1xx сотней, чтобы сервер не мог держать клиента вечно.

Все три ветки в стадии security-only: релизы только исходниками, без установщиков. 3.10 получает исправления до октября этого года, 3.12 — до октября 2028-го.

@zen_of_python

Читать полностью…

Zen of Python

Ruff включил по умолчанию 413 правил вместо 59

Версия 0.16.0 вышла 23 июля. Заодно 18 самых спорных правил групп pycodestyle и pyflakes из набора по умолчанию убрали.

🔘 форматирование блоков Python внутри Markdown включено по умолчанию — документация в репозитории теперь тоже под форматтером;
🔘 подавляющий комментарий ruff: ignore можно ставить в конце строки, как noqa, или на строке перед диагностикой;
🔘 исправления показываются прямо в выводе check и format --check, вместе с кусочком диффа;
🔘 format --check получил форматы вывода линтера, включая github и gitlab для аннотаций в сборке;
🔘 в JSON-выводе поля filename, location и end_location теперь могут быть null — разборщики придётся проверить;
🔘 стабилизированы двенадцать правил.

Обновление стоит планировать отдельной задачей: на первом прогоне вылезет то, что раньше молчало, и часть находок потребует решения — включать правило, исключать или править код.

@zen_of_python

Читать полностью…

Zen of Python

У библиотеки websockets появилась реализация на Trio

Версия 17.0 вышла 29 июля. Раньше выбора не было: под капотом всегда asyncio либо потоки. Теперь третий вариант — Trio, для тех, кто уже строит на нём приложение.

Релиз ломающий, и вот что придётся проверить перед обновлением:

🔘 минимальная версия Python поднята до 3.11, последней с поддержкой 3.10 остаётся ветка 16.1;
🔘 удалены псевдонимы модулей, которые перенесли ещё в девятой версии;
🔘 несколько логических аргументов у send(), ping() и broadcast() стали передаваться только по имени;
🔘 не-ASCII заголовки рукопожатия теперь кодируются в ISO-8859-1; прежнее поведение было недокументированным и не совпадало со спецификацией HTTP;
🔘 в реализации на потоках аргумент socket переименован в sock;
🔘 process_request теперь получает и запросы с методом, отличным от GET, и запросы по HTTP/1.0, вместо того чтобы соединение просто закрывалось.

Из добавленного: broadcast() и Server.connections в реализации на потоках, аккуратное закрытие соединений при остановке, reconnect_delays для настройки пауз между попытками переподключения, а на некорректное рукопожатие сервер теперь отвечает кодами 405 и 505 вместо тишины.

@zen_of_python

Читать полностью…

Zen of Python

Технически всё верно...

Читать полностью…

Zen of Python

Бинарный поиск ускорили в 8 раз, не меняя ни алгоритм, ни язык: 16,6 процента промахов предсказателя ветвлений превратились в ноль

Итамар Тёрнер-Трауринг разобрал шаг из градиентного бустинга в scikit-learn: миллион чисел с плавающей точкой нужно разложить по 255 корзинам, для чего по массиву границ гоняется бинарный поиск. Код уже скомпилированный и уже параллелится по ядрам, алгоритм оптимальный. Статья опубликована 11 июля и обновлена 18-го, работа сделана в рамках Quansight.

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

🔘 исходная версия: 45 200,4 микросекунды, 16,6 процента неверных предсказаний, 0,7 инструкции за такт, около 27 инструкций ветвления на одно значение;
🔘 первая переделка убирает ветвление, заменяя его условным присваиванием через select_unpredictable: 9 685,2 мкс, промахов ноль, 3,2 инструкции за такт, ветвлений 19 на значение;
🔘 предвычисление половины диапазона и доступ без проверки границ дают 7 280,4 мкс и обрушивают число инструкций ветвления до 6 020 571 на весь прогон;
🔘 финальная версия обрабатывает значения кусками по 16 штук с внешним циклом по шагам поиска: 5 453,4 мкс и 4,9 инструкции за такт;
🔘 любопытно, что она выполняет больше инструкций, чем предыдущая, но идёт быстрее: независимые куски работы позволяют процессору выполнять их одновременно;
🔘 итог — примерно восьмикратное ускорение на том же алгоритме, том же языке и одном ядре.

Автор оговаривает, что это упрощённый пример, а не полная реализация из scikit-learn, и что статья не заменяет учебник по устройству процессора. В обновлении он честно пишет, что раньше ошибся со сравнением чисел с плавающей точкой, и после исправления SIMD перестал давать выигрыш, хотя код от этого стал только быстрее. Дальнейший запас автор видит в параллелизме по ядрам.

Польза для питониста прямая: когда расчёт уже переписан на расширение и всё равно кажется медленным, следующий уровень — не алгоритм, а поведение процессора на этом коде.

Полная статья: https://pythonspeed.com/articles/branchless-binary-search/

@zen_of_python

Читать полностью…

Zen of Python

Процесс дорос до 57 гигабайт памяти и был убит ядром, потому что к каждой задаче прикреплялся живой стек вызовов

Хорхе Эскобар описал 27 июля в трекере playwright-python редкий вид утечки, где виноваты сразу две стороны. Синхронная обёртка библиотеки на каждый вызов создаёт задачу asyncio и вешает на неё атрибут со значением inspect.stack(0). Это не строки, а объекты FrameInfo, внутри которых лежат живые кадры стека вместе со всеми локальными переменными вызывающего кода.

Сама по себе такая привязка живёт ровно до конца вызова. Но на Python с 3.14.0 по 3.14.6 была регрессия: asyncio.wait с FIRST_COMPLETED навсегда оставлял вызывающую задачу в множестве ожидающих у того будущего, которое так и не завершилось. Playwright задевает это на каждом обращении к браузеру, потому что гонит ответ на команду наперегонки с долгоживущим будущим ошибки транспорта. В итоге каждая завершённая операция утаскивала за собой задачу, кадры и всё их содержимое.

🔘 нагрузка простая: скриншот на каждый кадр, PNG 3840×2160 декодируется через PIL прямо в той функции, которая вызывает page.screenshot();
🔘 на итерацию оставалось около 40 МБ: примерно 33 МБ распакованного изображения и ещё около 8 МБ уменьшенной копии, обе как локальные переменные удержанного кадра;
🔘 после примерно 1900 итераций процесс занял около 57 ГБ и получил SIGKILL;
🔘 gc.collect() не помогает вообще: запись в множестве ожидающих является сильным корнем, а не циклической ссылкой;
🔘 цепочка ссылок читается целиком: изображение, кадр вызывающей функции, FrameInfo, завершённая задача скриншота, множество ожидающих у будущего ошибки транспорта, которое живёт всю сессию браузера;
🔘 перестройка своего кода так, чтобы во время вызова в кадрах не было больших локальных переменных, снизила утечку с 40 МБ до 0,94 МБ на итерацию.

Обе стороны уже починены: регрессию в CPython закрыли 2 июля, а в playwright-python убрали хранение живых кадров, и 30 июля обсуждение закрыли. Автор замерял на конкретной нагрузке и на macOS с Python 3.14.6, но структурно то же поведение видел и на 3.12.

Вывод из этой истории шире одной библиотеки: пока задача жива, живо и всё, на что смотрят её кадры. Прикреплять inspect.stack() к объектам, время жизни которых вы не контролируете, означает подписаться на удержание чужих локальных переменных.

Обсуждение целиком: https://github.com/microsoft/playwright-python/issues/3157

@zen_of_python

Читать полностью…

Zen of Python

Многопоточный NumPy на сборке без GIL был в 7 раз медленнее процессов, стал в 4 раза быстрее

Всё началось с вопроса на Stack Overflow: пользователь пожаловался, что на сборке CPython без GIL расчёт через ThreadPoolExecutor заметно медленнее того же расчёта через ProcessPoolExecutor. Кумар Адитья из Quansight Labs описал 29 июля, как несколько месяцев вычищал причины этого в NumPy и самом CPython.

Нагрузка простая и типичная для ufunc: каждый работник берёт свой массив, гоняет по нему np.sin и np.cos в цикле и сворачивает результат. Общего изменяемого состояния между потоками нет, так что масштабироваться оно должно линейно. На деле до 18 потоков росло, а дальше резко деградировало: на 32 работниках 44 секунды против 6 у процессов. Профилирование через samply показало три класса проблем: конкуренция за блокировки, конкуренция за счётчики ссылок общих объектов и конкуренция в аллокаторе.

🔘 tracemalloc выключен по умолчанию, но всё равно брал глобальную блокировку на каждом выделении и освобождении памяти, просто чтобы проверить, включён ли он. Теперь проверка идёт атомарной операцией без блокировки;
🔘 кэш диспетчеризации ufunc, который сопоставляет типам аргументов конкретную реализацию, жил под std::shared_mutex. Записи в нём неизменяемы, поэтому чтение сделали полностью свободным от блокировок, мьютекс остался только на редкие вставки;
🔘 указатель на аллокатор памяти NumPy хранит в глобальном объекте PyCapsule. Без GIL каждое обращение к нему меняет счётчик ссылок атомарно, и кэш-линия со счётчиком начинает метаться между ядрами. Объект сделали бессмертным, то есть вообще без подсчёта ссылок;
🔘 ради этого в CPython появился публичный PyUnstable_SetImmortal: с 3.15 он доступен всем, на 3.14 его можно взять через pythoncapi-compat;
🔘 запись np.sin — это поиск атрибута в модуле, а специализация байткода для таких поисков не работала, если модуль определяет __getattr__. Каждый вызов уходил на медленный путь с захватом импортной блокировки;
🔘 массивы NumPy выделял системным malloc, который плохо переносит параллельные выделения, особенно на macOS. Сырой аллокатор CPython в сборке без GIL перевели на mimalloc, а NumPy переключили на этот сырой аллокатор.

После всех правок та же задача на 32 ядрах занимает около 1,5 секунды: примерно в 30 раз быстрее, чем было, и вчетверо быстрее варианта с процессами. Автор оговаривает, что замеры сделаны на одной 32-ядерной машине с Linux и на конкретной ufunc-нагрузке без общего состояния между потоками.

Полная статья: https://labs.quansight.org/blog/scaling-numpy-on-free-threaded-python

@zen_of_python

Читать полностью…

Zen of Python

Как в CPython 3.15 убрали проверку из горячего цикла интерпретатора

JIT в CPython нужен поток исполняемых инструкций, значит интерпретатор должен уметь записывать всё, что выполняет. Ключевой вопрос в том, сколько за эту возможность платят программы, которым запись не нужна. Кен Джин описал 1 июля путь к решению, попавшему в 3.15.

Первый вариант: два отдельных интерпретатора, обычный и записывающий. Для интерпретатора на хвостовых вызовах это работало приемлемо, а на варианте с computed goto дало около 6% замедления на pyperformance. Одна из причин в том, что код интерпретатора на C фактически удвоился и перестал помещаться в кэш процессора. Второй вариант, флаг режима записи, убирает раздувание кода, но возвращает проверку ветвления в самый горячий путь, на каждую выполняемую инструкцию.

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

🔘 наивная реализация с двумя полноценными таблицами упирается в ту же проблему, это снова два интерпретатора;
🔘 трюк в том, что все записи второй таблицы указывают на одну-единственную инструкцию записи;
🔘 она пишет trace и передаёт управление обычной таблице, получается сужение потока и обратное расширение;
🔘 включение и выключение режима сводится к подмене указателя на таблицу, ENTER_TRACING() и LEAVE_TRACING();
🔘 в игрушечном замере медиана по 40 запускам составила 1,72 микросекунды без записи и 7,47 микросекунды с записью и JIT.

Автор оценивает накладные расходы максимум в 4,5x и сразу оговаривает, что сравнивать это с накладными расходами 900–1000x у PyPy некорректно.

Чем профилируете горячий Python-код сейчас и во сколько раз он от этого замедляется?

@zen_of_python

Читать полностью…

Zen of Python

Шесть проверщиков типов для Python на одних и тех же исходниках

Справочник pydevtools прогнал mypy, pyright, Basedpyright, ty, Pyrefly и Zuban по скорости, строгости, выводу типов, поддержке в редакторах и лицензиям. Замеры делались на Apple Silicon, по пять запусков с отброшенным прогревом и очищенными кэшами, дата последнего обновления страницы 30 июля.

На библиотеке Rich, 38 тысяч строк: mypy 0,90 с, pyright 2,64 с, Basedpyright 2,35 с, ty 0,10 с, Pyrefly 0,17 с, Zuban 0,13 с. На SQLGlot, 76 тысяч строк: mypy 2,58 с, pyright 4,04 с, Basedpyright 4,33 с, ty 1,28 с, Pyrefly 0,32 с, Zuban 0,49 с.

🔘 в этом наборе ty обогнал mypy в 9 раз на Rich и в 2 раза на SQLGlot, Pyrefly быстрее в 5–8 раз, Zuban в 5–7 раз, pyright же оказался в 2–3 раза медленнее mypy;
🔘 параллельный режим mypy на проектах такого размера дал 1,2x и 1,3x вместо обещанных пятикратных;
🔘 в наборе тестов на соответствие спецификации типов из 141 пункта: Zuban 140, pyright 134, Pyrefly 130, ty 100, mypy 83;
🔘 mypy по умолчанию вообще не заглядывает внутрь функций без аннотаций, остальные разбирают их выводом типов;
🔘 расхождение видно на двух строках: после x = [] и x.append(1) pyright, Basedpyright и ty считают тип list[Unknown], а mypy, Pyrefly и Zuban выводят list[int].

Автор предупреждает, что процент прохождения тестов на соответствие не означает практической пользы: пункты в наборе неравноценны по важности, а инструменты в бете меняются быстро, так что цифры стареют.

Переезжали с mypy на что-то из нового поколения, и сколько чужих ошибок вылезло на неаннотированном коде?

@zen_of_python

Читать полностью…

Zen of Python

Создаём программу, которая сама умеет выбирать модели в зависимости от задачи

Чем дольше работаешь с ИИ, тем больше убеждаешься, что под разные задачи нужны разные модели. Дя простых задач не нужна дорогая модель, а со сложной дешевая может не справиться. А ещё, используя только одну модель, вы рискуете получить кирпич, если эта модель вдруг станет недоступна.

Выход — разделить запросы по сложности и держать под рукой запасной вариант. Сначала ваш скрипт решает, насколько труден вопрос, потом отправляет простое к дешёвой модели, а сложное — к мощной. При сбое переключается на резервную модель. Туториал разбирает это на Python шаг за шагом.

#python

Читать полностью…

Zen of Python

PyPI перестал принимать новые файлы в релизы старше четырнадцати дней

Сет Ларсон описал это изменение 22 июля. Правило простое: если версия пакета опубликована больше двух недель назад, добавить в неё новый файл больше нельзя. Цель — закрыть сценарий, при котором давно стабильную версию тихо дополняют после угона токена или доступа к сборочному процессу. Подтверждённых злоупотреблений на момент публикации не было.

🔘 обсуждение началось ещё вокруг PEP 740 в январе 2024 года и вернулось в марте 2026-го после компрометации двух проектов;
🔘 исправление влили 8 июля 2026 года;
🔘 главное возражение бытовое: проекты иногда докладывают колёса для новой версии Python в уже выпущенный релиз;
🔘 масштаб возражения измерили: среди 15 тысяч самых популярных пакетов так поступили только 56 проектов, добавив совместимое с CPython 3.14 колесо позже чем через две недели;
🔘 на профильной встрече в рамках PyCon US 2026 сочли приемлемым требование выпускать под новый Python новую версию пакета;
🔘 автор просит пока не закладываться на это правило как на гарантию: семантика закрытого релиза и соответствующий интерфейс ещё не определены.

Окончательная модель должна приехать вместе с новым протоколом загрузки и предварительными релизами, после стандартизации PEP 694. Так что практический вывод для авторов пакетов такой: план поддержки нового CPython теперь придётся строить через выпуск новой версии, а не через дозагрузку колеса в старую.

@zen_of_python

Читать полностью…

Zen of Python

Двадцать пять минут, которые снимают половину вопросов новичка: доклад Неда Бэтчелдера «Facts and Myths about Python names and values» с PyCon US 2015.

Весь доклад отвечает на один вопрос: что происходит, когда вы пишете x = 23. Ответ — не «в переменную кладётся значение», а «имя привязывается к объекту». Из этого простого сдвига вырастает объяснение почти всех классических непоняток:

🔘 почему Python нельзя описать ни как передачу по значению, ни как передачу по ссылке;
🔘 почему два имени могут указывать на один объект и менять его вместе;
🔘 почему присваивание никогда не копирует объект;
🔘 почему += для списка и для числа ведут себя по-разному;
🔘 почему изменяемый аргумент по умолчанию живёт между вызовами;
🔘 почему аргументы функции, атрибуты объекта и переменная цикла — это всё одна и та же операция связывания имени.

Бэтчелдер разбирает это на диаграммах с коробочками и стрелками, без единой строчки про внутренности интерпретатора. Именно поэтому доклад одиннадцатилетней давности не устарел ни на строчку: семантика имён и объектов с тех пор не менялась.

@zen_of_python

Читать полностью…

Zen of Python

Cython научился подсказывать компилятору C, какая ветка вероятнее

14 августа вышла бета 3.3.0b1. Релиз для тех, кто собирает расширения: в нём несколько вещей, которые раньше приходилось обходить руками.

🔘 появились cython.likely() и cython.unlikely() — подсказки оптимизатору компилятора C о том, какая ветка условия ожидается чаще;
🔘 реализована конструкция except * для групп исключений по PEP 654 на Python 3.11 и новее;
🔘 аннотации типов у глобальных переменных теперь участвуют в выводе типов, а не игнорируются;
🔘 у сеттеров свойств, объявленных на C, появилась возможность явно пробрасывать исключение вместо безусловной проверки PyErr_Occurred() после каждого вызова;
🔘 поддержан синтаксис однородных кортежей вида tuple[atype, ...];
🔘 набор возможностей общего модуля теперь настраивается при сборке: команда cython generate-shared принимает --only и --exclude, так что в модуль попадает только нужное.

Последний пункт стоит читать вместе с предупреждением авторов: возможность экспериментальная, и если при сборке выключить то, что реально используется, импорт упадёт уже во время работы. Отдельно поменялась схема имён при экспорте и импорте слитых функций на C, но старые имена оставлены для совместимости.

Есть и просто ускорения: форматирование чисел в f-строках, extend у bytearray байтами, проверки одиночного символа, размер асинхронных генераторов и перевод модулей с большим числом строк. Численных замеров в списке изменений нет, только слова «быстрее» и «меньше», так что судить о выигрыше на своём проекте придётся самому.

@zen_of_python

Читать полностью…

Zen of Python

Почему str.lower() иногда ломает проверку имён доменов

Регистр символа в Python зависит от версии Unicode интерпретатора. Сейчас unicodedata.unidata_version — 17.0.0, а протокол подготовки строк stringprep, который лежит в основе международных доменных имён, зафиксирован на Unicode 3.2.

Поэтому str.lower() и str.encode('idna') в разных версиях Python могут считать разные строки одинаковыми. В обычном коде это проходит незамеченным, а в проверке доменов или авторизации получается обход валидации.

В контексте безопасности не зовите str.lower() напрямую. Берите пакет idna: он реализует стандарт IDNA 2008 и не использует встроенные таблицы Unicode.

Seth Larson разбирает уязвимость и показывает, как это выглядит на практике.

Читать полностью…

Zen of Python

У Uvicorn появился разбор HTTP на Zig

Версия 0.52.0 вышла 29 июля и принесла экспериментальную реализацию HTTP/1.1. Работает она на библиотеке zttp: разбор там устроен без собственного ввода-вывода, ядро написано на Zig, а к Python приделаны привязки. Включается новый разборщик параметром --http zttp, схема запуска при этом не меняется.

🔘 сам разборщик перед публикацией несколько недель гоняли под фаззингом и провели несколько раундов проверки безопасности;
🔘 авторы прямо называют его экспериментальным и не советуют пускать через него боевой трафик;
🔘 сравнительных замеров задержки и пропускной способности в заметках нет вообще, так что выигрыш придётся мерить самому;
🔘 отдельно починена обработка заголовков WebSocket с символами вне ASCII в связке с библиотекой websockets версии 17.0;
🔘 в той же версии websockets кодирование таких заголовков переведено на ISO-8859-1, и это как раз причина расхождения;
🔘 обратную связь авторы просят присылать в трекер, то есть релиз рассчитан на тех, кто готов проверять на своей нагрузке.

Замеров ускорения авторы не приводят. Сам приём такой: разбор протокола вынесен в отдельную библиотеку без ввода-вывода, написанную на другом языке, а Python остаётся клеем и сокетным слоем. Проверить новый путь можно на тестовом стенде, ничего не меняя в боевом запуске: старая реализация остаётся на месте и остаётся выбором по умолчанию.

@zen_of_python

Читать полностью…

Zen of Python

Пять игр на Python, которые интересно почитать: они лежат в репозитории с настройками рабочего окружения Arch, и четыре из пяти обходятся стандартной библиотекой, а две самые большие рисуют интерфейс на curses.

🔘 tetris.py, 766 строк: все повороты фигур считаются заранее в build_rotations, при повороте у стены пробуются сдвиги, есть удержание фигуры и превью следующих, а когда разом уходят четыре линии, по экрану летят частицы;
🔘 snake.py, 423 строки: поле подгоняется под размер терминала, интервал тика уменьшается с каждым уровнем, голова заворачивается через край;
🔘 обе игры печатают через safe_addstr: он глотает curses.error, а в змейке ещё и обрезает строку по краю окна — иначе запись в последнюю ячейку роняет игру;
🔘 рекорды пишутся в общий game_high_scores.json так, чтобы не затирать записи соседних игр;
🔘 tetris_pygame.py — та же игра на pygame и dataclass, удобно сравнить два подхода к одной механике.

@zen_of_python

Читать полностью…

Zen of Python

@zen_of_python

Читать полностью…

Zen of Python

Django научился превращать классический N+1 в два запроса без единой правки в коде цикла

Django 6.1 вышел 5 августа. Главное изменение в релизе — режимы дозагрузки полей, то есть настройка того, что происходит в момент обращения к полю, которое из базы не приехало.

Раньше поведение было одно: подтягиваем недостающее поле для этого объекта. Теперь оно называется FETCH_ONE и остаётся по умолчанию, а рядом появились ещё два. FETCH_PEERS подтягивает поле сразу для всех объектов, приехавших из того же запроса, то есть работает как prefetch_related по требованию. FETCH_RAISE запрещает неявные обращения к базе вовсе.

🔘 режим ставится методом QuerySet.fetch_mode(), и цикл, который в каждой итерации трогает внешний ключ, начинает укладываться в два запроса вместо N+1;
🔘 FETCH_RAISE полезен в критичных по скорости участках: любой незамеченный поход в базу превращается в ошибку, а не в тихий лишний запрос;
🔘 у ForeignKey.on_delete появились варианты на стороне базы: DB_CASCADE, DB_SET_NULL и DB_SET_DEFAULT работают через SQL ON DELETE, и объекты для удаления загружать не нужно;
🔘 плата за это честно описана: DB_CASCADE не вызывает сигналы pre_delete и post_delete, потому что Python в удалении не участвует;
🔘 настройка MAILERS позволяет описать несколько почтовых движков с разными параметрами, как это давно сделано для кэшей и баз; в Django 7.0 она заменит EMAIL_BACKEND, пока старая настройка работает с предупреждением;
🔘 число итераций в хешировании паролей PBKDF2 поднято с 1 200 000 до 1 500 000.

По мелочи: добавлены функции UUID4 и UUID7, вычисляемые поля получили виртуальные колонки на PostgreSQL 18 и выше, RedirectView с сохранением метода запроса теперь отвечает кодами 307 и 308 вместо 302 и 301, а OpenLayers в админке обновлён с 7.2.2 до 10.9.0.

Поддерживаются Python 3.12, 3.13 и 3.14. Обычная поддержка релиза продлится примерно до апреля 2027 года, расширенная до декабря. В заметках отдельно перечислены несовместимости, так что перед обновлением их стоит прочитать: смена семантики сигналов при каскадном удалении на стороне базы как раз тот случай, когда код продолжает работать, а побочные действия молча исчезают.

@zen_of_python

Читать полностью…

Zen of Python

Новые модели выходят быстрее, чем все успевают их внедрять, а прогнозы про ИИ устаревают ещё быстрее, поэтому на кафедре техпреда МФТИ запустили «Точку сборки».

Это серия открытых вебинаров, на которых спикеры делятся опытом внедрения агентов, быстрой сборки MVP и конкретными кейсами своих команд.

Больше о «Точке сборки», о первом вебинаре и о том, как попасть на следующие читайте в новом материале на Tproger: https://tproger.ru/articles/pochemu-opyt-razrabotchikov-luchwe-prognozov-pro-ii

Читать полностью…

Zen of Python

Проверка одной группы объектов стоит времени, пропорционального размеру группы, а полный проход сборщика — всему содержимому процесса

28 июля на discuss.python.org появилось предложение научить сборщик мусора принимать подсказки: разработчик или анализатор кода говорит, что вот эта группа объектов, скорее всего, больше никому не нужна, а сборщик проверяет и освобождает её, не запуская полный обход поколения.

Смысл в том, как в CPython устроено освобождение памяти. Основную работу делает подсчёт ссылок, он мгновенный и точный. Но объекты, ссылающиеся друг на друга по кругу, счётчиками не убираются: у запроса лежит ссылка на его статус, у статуса обратная ссылка на запрос, оба недостижимы снаружи, а счётчики у обоих равны единице. За такие случаи отвечает отдельный сборщик циклов, и он останавливает все потоки, чтобы обойти память.

🔘 предлагаемая проверка простая: просуммировать счётчики ссылок объектов группы и посчитать, сколько ссылок на них идёт изнутри самой группы. Совпало — снаружи на группу никто не смотрит, можно освобождать;
🔘 в примере с запросом и статусом обе суммы равны двум, поэтому пара удаляется без обхода кучи;
🔘 подсказка ничего не гарантирует и всегда проверяется, а если из какой-то категории приходит слишком много ложных подсказок, её можно перестать слушать;
🔘 автор ссылается на известный случай Instagram, где сборщик отключали и просто перезапускали машины по мере роста памяти, и на статистику, по которой сборка мусора съедает от 5 до 30 процентов процессорного времени;
🔘 в обсуждении сразу возразили: Терри Ридди спрашивает, насколько часто цикл нельзя просто разорвать руками, а Грег Юинг — есть ли алгоритм, который окажется дешевле честной сборки;
🔘 ещё одно возражение практическое: подсказки придётся обновлять при каждой правке структур данных и как-то проверять тестами, а если уж писать такой тест, проще искать сами циклы и разрывать их.

Замеров у автора нет, работающего кода тоже: это предложение и спор вокруг него. Но читать его полезно из-за самого разбора механики: видно, что подсчёт ссылок и сборщик циклов решают разные задачи, и что цена второго определяется размером всей кучи, а не количеством мусора.

Обсуждение: https://discuss.python.org/t/improving-python-garbage-collection-performance-by-providing-unreachable-cycles-hints/108307

@zen_of_python

Читать полностью…

Zen of Python

На маленьком проекте подмена времени стоит 1406,7 микросекунды против 1,5, на большом — 40 971,3 против тех же 1,5

Адам Джонсон, автор time-machine, замерил 3 августа, как две библиотеки подмены текущего времени ведут себя с ростом проекта. Прошлый такой замер он делал в 2021 году и получил разницу в 100–200 раз, теперь захотел показать не отношение, а саму зависимость от размера кодовой базы.

Стенд простой: генерируются пустые модули по 25 атрибутов, каждый десятый держит ссылки на date, datetime и time, как будто их импортировали по имени. Замеряется цикл включения и выключения подмены. Python 3.15, MacBook на M1, freezegun 1.5.5 и time-machine 3.3.0.

🔘 259 модулей, то есть почти голый интерпретатор: 1406,7 мкс против 1,5 мкс, разница в 940 раз;
🔘 1259 модулей, размер небольшого проекта на Django: разница в 2490 раз;
🔘 16 259 модулей, крупный проект с обвесом зависимостей: 40 971,3 мкс против неизменных 1,5 мкс, разница в 27 300 раз;
🔘 время freezegun укладывается в прямую: около 1,4 мс постоянных расходов плюс 2,5 мкс на каждый модуль;
🔘 в наборе из 2000 тестов с подменой времени это 82 секунды чистой замены ссылок против 3 миллисекунд;
🔘 причина в том, что freeze_time.start() обходит весь sys.modules и подменяет каждую найденную ссылку на настоящие функции; даже при попадании в кэш приходится перечислить и захешировать имена атрибутов всех модулей.

У обхода есть и содержательная плата, помимо скорости: он не находит ссылки в атрибутах классов, значениях по умолчанию, замыканиях и C-расширениях, и они продолжают отдавать настоящее время. Плюс подставленные объекты видны по типу, datetime.__name__ внутри подмены становится FakeDatetime, и код, который смотрит на типы, может повести себя иначе.

time-machine вместо обхода переписывает указатель ml_meth в структуре PyMethodDef у встроенных функций, читающих часы. Таких функций десять, и это ровно десять записей в память независимо от размера проекта.

Автор оговаривает, что модули в стенде синтетические, это types.ModuleType, положенные прямо в sys.modules, а не настоящие импорты, и что замеряется только цикл подмены, из нескольких прогонов берётся минимальное время.

Полная статья: https://adamj.eu/tech/2026/08/03/python-time-machine-o1-freezegun-on/

@zen_of_python

Читать полностью…

Zen of Python

Python 3.15 добрался до rc1: ленивые импорты, frozendict и UTF-8 по умолчанию

4 августа вышел первый релиз-кандидат. Финал ожидается в октябре, так что смотреть, что придётся чинить, стоит уже сейчас. Самое заметное:

— PEP 810, ленивые импорты. Новый синтаксис lazy import json и lazy from json import dumps. Модуль не грузится, пока имя реально не тронули. Строго opt-in: меняется поведение только помеченных строк. Главные бенефициары — CLI-утилиты и тест-сьюты с тяжёлым деревом зависимостей.

— PEP 686, UTF-8 по умолчанию. Кодировка для I/O больше не зависит от системной локали. Откатить можно через PYTHONUTF8=0 или -X utf8=0 — и вот это стоит проверить заранее, если у вас Windows и legacy-файлы в cp1251.

— PEP 814, встроенный frozendict. Наконец-то неизменяемый словарь в ядре, а не в трёх конкурирующих пакетах на PyPI.

— PEP 798, распаковка в comprehensions. [*L for L in lists] для плоского списка, {**d for d in dicts} для слияния словарей — синтаксическая дыра, которая раздражала лет десять.

— PEP 799, семплирующий профайлер Tachyon прямо в стандартной библиотеке. Другой класс инструмента, чем cProfile: тот инструментирует каждый вызов и искажает картину на горячем коде, семплирующий периодически снимает стек и почти не мешает.

— JIT прибавил 6–7% среднего геометрического на x86-64 Linux и 12–13% на AArch64 macOS.

Плюс по мелочи: sentinel из PEP 661, TypedDict с типизированными extra-полями (PEP 728), TypeForm (PEP 747) и более внятные подсказки в AttributeError.

Полный список — в whatsnew, там же примеры кода и раздел с несовместимостями.

#python315 #cpython

Читать полностью…

Zen of Python

Разбор HTML-страницы за 272 микросекунды вместо 15,3 миллисекунды

Бернат Габор собрал turbohtml, набор инструментов для HTML целиком на C: экранирование, разэкранирование, токенизация, запросы по селекторам, сериализация и операции с URL. Разбор страницы на 92 КБ занимает 272 мкс против 15,3 мс у BeautifulSoup, запрос по селектору 1,3 мкс против 20,8 мкс у lxml и 99,9 мкс у BeautifulSoup, токенизация 34,9 мкс против 435 мкс у html.parser и 836 мкс у html5lib. На плотном четырёхмегабайтном тексте escape отрабатывает за 4,98 мс против 12,7 мс у html.escape. Публикация от 18 июня обновлена 10 июля.

Скорость складывается из того, как обрабатываются байты. Обычный код ищет спецсимволы посимвольно, и процессор на каждом байте выполняет ветвление. Здесь байты обрабатываются пачками: SWAR складывает восемь байт в одно машинное слово и проверяет их арифметикой, SIMD делает то же самое сразу по шестнадцать байт одной инструкцией. Ветвлений в горячем цикле почти не остаётся.

🔘 длина результата сначала считается точно, потом выделяется одним куском и заполняется массовым копированием, вместо дописывания по мере разбора;
🔘 токенизатор сохраняет исходную ширину строки и компилируется в три варианта под UCS-1, UCS-2 и UCS-4;
🔘 чистые текстовые куски возвращаются как срезы без копирования, копия делается только когда она правда нужна;
🔘 переписывание css_path() с квадратичного перебора на индекс сократило пример со 112 мс до 0,9 мс;
🔘 список переиспользуемых обёрток ускорил find_all() с 1,9 до 1,4 мкс, но в сборке без GIL он отключён.

Автор оговаривает, что Callgrind считает инструкции, а не реальное время по часам, поэтому на него опираются только при сравнении вариантов. Корректность проверяется побайтовым сравнением с эталонными библиотеками, сборками с ASan и UBSan и фаззингом.

Что у вас парсит HTML в проде и сколько времени на это уходит?

@zen_of_python

Читать полностью…

Zen of Python

Rust научился ускорять операции с плавающей точкой, сохраняя контроль за точностью

Если вы держите Rust под рукой для кусков, где Python не хватает скорости, в Rust 1.98 появился повод присмотреться. Новый API разрешает компилятору агрессивнее оптимизировать операции с плавающей точкой, но сохраняет контроль за точностью округления. Это значит, что Rust-код, который вызывается из Python, может считать быстрее без привычного компромисса «скорость против корректности».

Раньше в Rust не было стабильного способа управлять этим компромиссом. Теперь можно явно указать компилятору, где безопасно ускорять вычисления, а где важна минимальная погрешность.

Разобрался Itamar Turner-Trauring: если пишете Rust-расширения для Python или интересуетесь, откуда берётся скорость в числовых библиотеках, загляните.

Читать полностью…
Subscribe to a channel