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
Как коллизии превращают построение set в Python в квадратичную задачу

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

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

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

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

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

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

Раньше браузерная сборка NumPy выполняла np.matmul обычными циклами на C, которые не учитывали кэш процессора и векторные инструкции. Теперь NumPy 2.5.3 из Emscripten-forge по умолчанию подключает OpenBLAS 0.3.34 для WebAssembly. На квадратных матрицах 1024 × 1024 умножение ускорилось примерно в 30,92 раза для float32 и в 14,90 раза для float64.

Прирост зависит от операции. Функции np.linalg ускорились слабее, поскольку реализация LAPACK ещё не оптимизирована специально для WebAssembly. Экспериментальная OpenBLAS 0.3.35 добавляет новые векторные ядра, а сборка с Relaxed SIMD даёт дополнительное ускорение только в браузерах с поддержкой этого расширения.

Бенчмарки и настройки стенда помогают сравнивать сборки по размеру матриц, типу данных и браузеру, а не переносить одну красивую цифру на весь NumPy.
🙏1🍾1
Как проверять плагины через контракт Pydantic и FastAPI

Сервис может отвечать по нужным адресам и всё же возвращать несовместимые данные. В схеме общего API-контракта модели Pydantic для /ready, /catalog и /encode/ вынесены в отдельный пакет.

Плагин использует эти модели в обработчиках FastAPI, а платформа проверяет ими ответы при регистрации. Если структура не совпадает, сервис не подключается.

Контракт фиксирует и ответственность: разбиением входных данных управляет плагин, а проверки доступа вынесены в зависимости FastAPI. Это ловит несовместимый формат до подключения, но не проверяет правильность вычислений.
😨2🤯1😘1
Как битовые множества сокращают память моделей Pydantic

Explicit is better than implicit, но хранить набор явно переданных полей в полноценном Python set оказалось накладно. Memray показал: создание model_fields_set заняло почти 800 МБ на миллионе экземпляров модели с семью полями.

Поскольку поля модели упорядочены, каждому можно назначить номер бита: 1 означает, что значение передали при создании, 0 — что использовано значение по умолчанию. Имена дополнительных полей, которых нет в схеме, остаются в отдельном set.

В разборе реализации показано, как выбирать u8, u16 или вектор для моделей разного размера. На тесте с небольшими значениями замена сократила память на 55%. Полезный пример того, почему профилировать стоит не только __dict__, но и служебные структуры каждого экземпляра.
❤1☃1
Как отменять задачи в heapq и сохранять порядок при равных приоритетах

heapq сравнивает кортежи по полям. Поэтому запись (приоритет, задача) при равных приоритетах либо сравнит сами задачи, либо упадёт, если объекты несравнимы. Надёжная форма: (приоритет, номер, задача), где номер выдаёт itertools.count(). Он сохраняет порядок добавления и не допускает сравнения задач.

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

У этой схемы очереди есть цена: устаревшие записи занимают память, пока не доберутся до вершины. Если обновлений много, а извлечений мало, кучу стоит периодически пересобирать из активных записей.
❤1👀1
Как воспроизводить гонки потоков Python с помощью blanket

Обычно порядок выполнения потоков выбирает ОС, поэтому редкая гонка превращает тест в лотерею. blanket оборачивает примитивы threading, а тест сам решает, какой поток продолжит работу. Пять повторов перестают быть стратегией тестирования.

В Scenario можно заставить поток B захватить блокировку раньше A, задать порядок освобождения барьера или выбрать поток, который разбудит notify(). Каждый такой вызов приостанавливается в планировщике blanket, поэтому сценарий повторяется одинаково.

Это полезно при переносе библиотек на Python без GIL, но управление порядком потоков работает на границах примитивов синхронизации. Для гонки внутри голого counter += 1 нужна дополнительная контрольная точка. blanket закрепляет известный порядок в регрессионном тесте, а не перебирает все возможные гонки.
🔥2🥰1
Пакетная обработка в Python: где strict=True не спасает запись

С Python 3.12 itertools.batched() лениво забирает из итератора элементы только для очередного кортежа. Поток из файла, курсора или генератора не приходится сначала превращать в список: памяти для группировки нужно столько, сколько занимает один пакет, а не весь источник.

С Python 3.13 параметр strict=True отклоняет неполный последний пакет. Но ошибка возникает во время итерации. При 250 записях и размере пакета 100 первые 200 уже могут попасть в базу, прежде чем оставшиеся 50 вызовут ValueError. Строгость проверяет форму входа, а не делает цикл транзакцией.

Явное лучше неявного: для записи по принципу «всё или ничего» заранее проверьте конечный источник либо используйте транзакцию системы назначения. Разбор itertools.batched() также объясняет, почему list(batched(...)) лишает обработку преимущества ленивости.
❤1
Как выстроить защиту цепочки поставок Python

Эшелонированная схема защиты начинается с собственного кода: Ruff находит секреты, слабую криптографию и запросы без тайм-аута до публикации пакета.

Зависимости и их криптографические хеши фиксируют через uv lock или uv pip compile --generate-hashes. Затем pip-audit ищет в CI известные уязвимости, а SBOM помогает понять, затронул ли проект компрометированный пакет. Trusted Publishing с OIDC заменяет долгоживущие API-токены.

Хеши не распознают изначально вредоносный пакет, а аудит пропускает неизвестные уязвимости. Поэтому начать стоит с Ruff и фиксации зависимостей, затем добавить аудит и SBOM.
Как не сломать pickle собственным исключением Python

Если конструктор исключения принимает field и reason, не заменяйте их готовой строкой при вызове super().__init__(). Иначе в args останется один элемент, а при восстановлении BaseException.__reduce__ передаст его обратно конструктору. Тот ждёт два аргумента, поэтому распаковка завершится TypeError. Так же ломаются copy.copy() и передача исключения между процессами.

Питоничный инвариант прост: args должны оставаться корректным списком аргументов для вашего __init__. Передавайте в базовый класс исходные field и reason, храните структурированные данные в атрибутах, а читаемое сообщение формируйте в __str__.

Пример с ValidationError показывает поломку и совместимый вариант класса.
🤪1
Охрана трубопровода длиной 400 километров

Камеры на столбах не заглянут за угол, дрон висит 30-40 минут и летит заряжаться, а охранник-человек — дорого, медленно и небезопасно на протяжённом объекте.
Технический разбор робопса изнутри: что скрыто под закрытым ядром биомеханики, какой стек сенсоров закрывает слепые пятна друг друга — лидар боится стекла, поэтому рядом ставят сонар.
Как рой из нескольких собак строит p2p-сеть, чтобы одна работала мостом для другой за горизонтом связи в техническом разборе робопса.
❤‍🔥1👍1
Как вернуть значение из всплывающего окна в форму Django Admin

Django Admin умеет создавать связанный объект во всплывающем окне и сразу подставлять его в исходную форму. Практический разбор этого механизма показывает, как переиспользовать его для собственного инструмента выбора значений.

Пример связывает ModelAdmin, отдельное представление, URL и шаблон. Ссылка вызывает showRelatedObjectLookupPopup, а кнопка во всплывающем окне через opener.dismissRelatedLookupPopup возвращает выбранную строку в поле формы и закрывает окно.

Питоничность здесь в композиции готовых частей: представление может вычислить значение любым способом, а JavaScript админки доставит результат в нужное поле. Цена переиспользования — зависимость от соглашений Django Admin: специального идентификатора ссылки и функций из RelatedObjectLookup.js.