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

Давайте сразу договоримся: я не публикую ИИ слоп, пишу статью ручками (потому что уважаю своего читателя) и рассказываю интересное и актуальное из своей бесплатной работы в опенсорсе, а с вас лайк статье (если она будет для вас интересной).
Погнали смотреть, что у нас в питоне происходило!
CPython
Начнем с самого важного и большого - с питона. Мой месяц в питоне прошел под знаком frozendict. Я старался активно улучшать поддержку иммутабельных типов в CPython.
Написал PEP и сделал Reference Implementation нового синтаксиса для frozendict / frozenset
Вместе с Донхи На (членом Steering Council) написали ПЕП для добавления нового синтаксиса frozen контейнеров в питон.
Такого синтаксиса явно не хватает. Наша главная задача: повысить частоту использования иммутабельных типов данных перед Free-Threading era.
fd = f{1: 2, 3: 4} assert isinstance(fd, frozendict) fs = f{1, 2} assert isinstance(fs, frozenset)
Абсолютно знакомый синтаксис. Ведь мы просто добавили f (как frozen) перед существующими dict и set литералами.
Поддерживаем все виды синтаксиса и comprehensions, конечно же:
f{1, *other, 2}f{**first, **second, 'default': 0}f{x for x in range(10) if x % 2 == 0}f{x: x for x in range(10)}f{x async for x in arange(10)}PEP-798
f{**items for nums in list_of_items}
Пока идея (PEP и реализация) в процессе доработки и обсуждения. Целимся в 3.16 :)
Предложил добавить новое C-API для оптимизации создания frozendict из dict
PR: https://github.com/python/cpython/pull/153413
Сейчас создание frozendict - довольно дорогая операция. PyFrozenDict_New вызывает dict_update_common, которые копирует данные из словаря за O(n). А мой PyDict_AsFrozenDictAndClear сделает такое за O(1).
Так как PyDictObject и PyFrozenDictObject C-структуры имеют очень похожее внутреннее устройство, то я могу просто выдрать куски из словаря и вставить их в новый frozendict.
Аллоцируем новый пустой frozendict, затем под локом - выдираем куски из старого словаря. Детали реализации (чуть упрощено для чтения):
PyObject * PyDict_AsFrozenDictAndClear(PyObject *dict) { if (dict == NULL || !PyDict_Check(dict)) { PyErr_BadInternalCall(); return NULL; } PyObject *res = frozendict_new_untracked(&PyFrozenDict_Type); if (res == NULL) { return NULL; } Py_BEGIN_CRITICAL_SECTION(dict); transfer_keys_and_values_lock_held(res, dict); Py_END_CRITICAL_SECTION(); _PyObject_GC_TRACK(res); assert(_PyFrozenDictObject_CAST(res)->ma_hash == -1); return res; }
Хеш во frozendict считается лениво по запросу __hash__, а не сразу при создании; потому он остается -1.
А вот как мы выдираем куски (забираем ключи и значения), очищаем входной словарь:
static void transfer_keys_and_values_lock_held(PyObject *res, PyObject *dict) { PyDictObject *new = (PyDictObject *)res; PyDictObject *old = (PyDictObject *)dict; assert(can_modify_dict(old)); // Fast path: do nothing on an empty dict: if (old->ma_keys == Py_EMPTY_KEYS) { return; } Py_ssize_t used = old->ma_used; PyDictKeysObject *keys = old->ma_keys; PyDictValues *values = old->ma_values; // Clear the old dict keys and values, but do not decref them: clear_common(old); set_keys(old, Py_EMPTY_KEYS); set_values(old, NULL); ASSERT_CONSISTENT(old); // Transfer keys and values from dict to frozendict: new->ma_used = used; new->ma_keys = keys; new->ma_values = values; ASSERT_CONSISTENT(new); }
Прикольно, да? Такое же АПИ мы готовим для frozenset и set. Там все сложнее, к сожалению старые фукнции C-API для PySet умеют мутировать frozenset :( Надо будет такое запрещать, но для начала деприкейтить.
Поддержал добавление новых двух методов для dict и set
На основе моего АПИ Виктор Стиннер (топ2 по коммитам в CPython после Гвидо) предложил добавить два метода: dict.take_frozendict и set.take_frozenset, которые будут превращать мутабельные данные в имммутабельные данные (реализация). Он поделился со мной идеей, я был только за!
fs = {1, 2, 3}.take_frozenset() assert type(fs) is frozenset assert fs == frozenset({1, 2, 3})
У нас уже есть bytearray.take_bytes, который за O(1) и без копирования данных превращает bytearray в иммутабельный bytes.
Возможно, что появятся аналоги и для других типов.
Пофиксил очень крутой баг, который показывает опасность мутабельных типов данных
PR: https://github.com/python/cpython/pull/152483
То формально было 29 июня, но какая разница!
Вот такой простой код содержит в себе 2 критичных бага:
class Evil: def __eq__(self, other): return other leaked = vars(list) == Evil() name = "example" leaked[name] = lambda self: "probe" print(getattr(list, name)([])) del leaked[name] print(hasattr(list, name))
Почему?
Потому что types.MappingProxyType выставляет наружу внутренний тип (dict) при сравнении и операции |. И мы уже его можем полностью мутировать. А тут мы выставляем наружу list.__dict__, который можем мутировать таким хитрым способом. И мы мутируем ->tp_dict встроенного типа данных! Потом интерпретатор падает со стектрейсом от таких приколов.
Полный разбор бага (длинный текст!)
Я просто сделал PR, который отправляет копии данных в небезопасные классы. Сейчас, однако, идет обсуждение: возможно мое изменение нужно откатывать. Некоторые участники сообщества считают, что просто так делать не надо. И бага нет. Обсуждаем дальше!
msgspec
Для тех, кто не знает: самый быстрый json / msgspack сериализатор и десериализатор в Python. Написан на C, использует просто уйму оптимизаций.
В июне я рассказывал, почему msgspec такой быстрый, а в июле я добавил:
Поддержку
frozendictдля Python 3.15+Пофиксил всяких багов по мелочи
Ждем новый релиз, пока даем коду настояться ?️️
django-modern-rest
Самый быстрый, строгий и удобный REST API слой для Django. Если не знакомы, то я публиковал статью на хабре про него. В июле было сделано два новых релиза. Важные изменения:
Добавили Opaque Token аутентификацию
Добавили возможность тестирования тротлинга / рейтлимитов
Добавили встроенные классы для проверки корректности JWT токена
Добавили всякие удобные DX фишечки для пользователей, например
SyncOrAsyncAuthутилиту для объявления глобальных правил аутентификации в настройках:
>>> from dmr.settings import Settings >>> from dmr.security import SyncOrAsyncAuth >>> from dmr.security.http import HttpBasicAsyncAuth, HttpBasicSyncAuth >>> DMR_SETTINGS = { ... Settings.auth: [ ... SyncOrAsyncAuth( ... HttpBasicSyncAuth(), ... HttpBasicAsyncAuth(), ... ), ... ], ... }
wemake-python-styleguide
Самый строгий Python линтер ever! Лучший инструмент, чтобы научить вашего агента писать простой и понятный Python код.
За июль вышел новый релиз 1.7.0. В нем мы:
Запретили писать
array[start:stop:]вместоarray[start:stop]иarray[start::]вместоarray[start:]Улучшили правила для
match/case, например, такой код теперь заставят переписывать:
match state: case EventType.REJECT: user = 'rejected' case _: user = 'active'
Потому что тут у нас просто if state == EventType.REJECT: ... с else:, а не match/case. Аналогично с другими типами паттернов MatchSequence, MatchMapping, тд.
Одной строкой
Здесь будут ссылки и новости от сообщества. Хотите попасть сюда? Присылайте свои проекты в чат с тегом #opensource. Опубликую в следующем месяце.
Мониторинг работы GC в CPython: https://github.com/sergey-miryanov/gcmon
Визуализатор workflow для агентов: https://github.com/Sanexxxx777/agent-graph-inspector
Пишем фронтенд прямо на Go + Gox в wasm: https://github.com/graybuton/goframe
Одной строкой:
Выпустил 0.8.0 релиз DI фреймворка для питона
punqс поддежкой типизации: репозиторий | зеркалоВыпустил два новых релиза проекта
dotenv-linterдля проверки корректности ваших.envфайлов: больше правил, лучше возможности для игнорирования конкретных проблем: репозиторий | зеркалоВыпустил релиз типов для Django с поддержкой mypy 2.3 (последняя версия на данный момент) репозиторий | зеркало
Провел PythoNN митап в Нижнем Новгороде, если живете там (или хотите приехать на следующий), то следите за анонсами. У нас круто и познавательно!
Заключение
Фух, вроде бы все важное покрыли :) Расскажите, как вам формат? Было ли интересно? Было ли полезно?
Постараюсь выкладывать такие отчеты каждый месяц, если формат вам понравится.
А если вы думаете, что я делаю что-то полезное для мира, то поддержать можно по ссылкам:
Я там ничего не продаю, просто делаю свою работу, если считаете, что я делаю полезное - можно поддержать. Если нет, то я все равно продолжу ее делать.
Подписывайтесь на главный канал сообщества, если вам нравится такое читать. И возможно даже захочется поучаствовать! Буду рад видеть вас частью нашего опенсорс сообщеста :)
Давайте в коммментах обсудим: какой синтаксис для frozendict / frozenset вы бы хотели видеть в Python?
Комментарии (36)

danilovmy
31.07.2026 13:41дичь конечно, но как есть:
почему не упростить синтаксис доmydict.take_frosen() myset.take_frosen(). тогда я могу нормально работать сgetattr(obj, "take_frosen")()или по какой то причине необходимо использовать разные названия для одного и того же действия?
почему не стали использовать что нибудь типа myset as frosen?
Как будет проверяться что frosen? будет лиif myset is frosen:Форматтер строки к сетам/диктам.... о да,
это прекраснонет. Это же очередной выстрел в ногу. Потому что у строки есть/был u'', f'', r'', t'' и теперь f'' будет у dict но он делает не то что вы полумали. а с автодополнением f{1,2,3} в vscode превратится в строку... "1,2,3" и никак не в фрозенсет.
sobolevn Автор
31.07.2026 13:41Я почти ничего не понял :)
set.take_frozensetиdict.take_frozendict- разные методы для разных типов. их названия точно отражают то, что происходит внутри. Зачем сокращать названия builtin классов - не очень понятно.почему не стали использовать что нибудь типа myset as frosen?
Потому что в питоне нет концепции кастов.
asиспользуется для создания имен, а не типов. Добавлять такое - точно не надо :)Как будет проверяться что frosen?
Не очень понял вопрос. Как и сейчас?
isinstance(obj, frozenset)Форматтер строки к сетам/диктам
Нет, форматтер строки тут не при чем. Мы добавляем новый токен
f{, который называетсяFLBRACE, по аналогии с{(LBRACE).а с автодополнением f{1,2,3} в vscode превратится в строку
Автодопонение будет работать корректно. Сейчас же `{1, 2, 3}` не превращается в строку. И `f{1, 2, 3}` не будет. Не думаю, что тут могут возникнуть проблемы.

test4354545
31.07.2026 13:41Как будто fdict(ну или frozen_dict) вместо f более явно выражало бы суть. А то как то совсем не по дзену питона

sobolevn Автор
31.07.2026 13:41я не думаю, что тут есть реальная проблема. вы же просто помните, что
{}- словарь; вот так же иf{}- иммутабельный словарь.
test4354545
31.07.2026 13:41Дело, конечно, ваше. Скажу лишь, что когда я в первый раз познакомился с Python, я его полюбил как раз за то, что, в отличие от других языков, в нём вещей, которые нужно "просто помнить", было намного меньше и обычно всё упиралось в то, что нужно просто знать англ. язык.

sobolevn Автор
31.07.2026 13:41Как знание английского языка помогает вам понять `{1: 2}`? В чем принципиальное отличие от `f{1: 2}`?

test4354545
31.07.2026 13:41Никак, поэтому и топлю за то чтобы подобного было как можно меньше, а не больше :)

sobolevn Автор
31.07.2026 13:41Спасибо за вашу обратную связь! Вашу позицию не разделяю, но уважаю :)

morijndael
31.07.2026 13:41да, но компилироваться это будет примерно в следующее:
найти имя fdict в локальных/нелокальных/глобальных переменных
проверить, что его вообще можно вызвать
закинуть аргументы на стек
вызвать функцию
функция на C создаст нужный объект дергая внутренности интерпретатора
А литерал:
закинуть аргументы на стек
создать объект сразу нужным опкодом

sobolevn Автор
31.07.2026 13:41Вы совершенно правы :) Сейчас есть похожая штука: сравните
» ./python.exe -m dis <<< 'dict(a=1, b=2)' 0 RESUME 0 1 LOAD_NAME 0 (dict) PUSH_NULL LOAD_SMALL_INT 1 LOAD_SMALL_INT 2 LOAD_CONST 1 (('a', 'b')) CALL_KW 2 POP_TOP LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUEИ:
» ./python.exe -m dis <<< '{"a":1, "b": 2}' 0 RESUME 0 1 LOAD_CONST 0 ('a') LOAD_SMALL_INT 1 LOAD_CONST 1 ('b') LOAD_SMALL_INT 2 BUILD_MAP 2 POP_TOP LOAD_COMMON_CONSTANT 7 (None) RETURN_VALUEВроде разница не очень большая, но:
BUILD_MAPработает так:inst(BUILD_MAP, (values[oparg*2] -- map)) { PyObject *map_o = _Py_BuildMap_StackRefSteal(values, oparg); DEAD(values); ERROR_IF(map_o == NULL); map = PyStackRef_FromPyObjectStealMortal(map_o); }Где
_Py_BuildMap_StackRefStealделает тоже самое, что и простой вызов_PyDict_FromItems. Ну аCALL_KW- один из самых медленных опкодов вообще.

vasily-v-ryabov
31.07.2026 13:41Всё хорошо. Только метод назвал бы не take_frosendict, а as_frosendict. То же самое для frozenset. Так как-то понятнее и короче.
Ну, или хотя бы to_frozendict. Просто take - взять, а тут вроде отдавать надо. Поэтому и корёжит.

zzzzzzerg
31.07.2026 13:41У take семантика забрать значения, у as оставить на месте и вернуть копию. Можно было бы использовать steal, но от него решили отказаться в пользу существующего прецедента в bytearray.

aero2210
31.07.2026 13:41to_frozenпо аналогии сint.to_bytes
sobolevn Автор
31.07.2026 13:41to_bytesне уничтожает существующийint, а просто конвертит его. а тут нужен явный глагол, который бы показывал, что само левое значение обнулится:>>> b = bytearray(b'123') >>> b bytearray(b'123') >>> b.take_bytes() b'123' >>> b bytearray(b'')В питоне такое назвали
take_*:)
morijndael
31.07.2026 13:41надо было назвать
yoink_*, с ним сразу понятно что происходит с содержимым :D
banno
Стало выглядеть как f строки
sobolevn Автор
Да, таков наш план. Вот тут было первое голосование https://discuss.python.org/t/frozenset-and-frozendict-comprehensions/101584/48 Люди хотят, чтобы было похоже на
fстроку :) Я сам был за$vldmrmlkv
А если токен будет из двух букв - это нарушает какое-то соглашение в таких случаях или так исторически так сложилось?
foот frozen object тоже нормально и нет путаницы с f-строками.iroln
А зачем люди хотят, чтобы было похоже на f-строку? Синтаксический маркер
fуже используется в контексте строк. А теперь у этого маркера будут разные синтаксические роли в зависимости от типа литерала. Это по сути контекстно-зависимая семантика. И это выглядит как-то неоднозначно. Я бы так не делал.sobolevn Автор
Я не знаю, почему люди так хотят :)
Как я писал выше, я был за
$. Но демократия решила иначе.danilovmy
прочел все 75 комментариев. Люди хотят
frozenset(...)andfrozendict(...)offer much clearer mnemonic value. Зачем вводить f{}, когда возможно очевидноеfrozenset(iterable)непонятно. кстати предложенный{}.frosen(), кмк, идеальный вариант.Но, собственно, где я и где python...
sobolevn Автор
Но ведь
frozenset(iterable)не позволяет нормально оптимизировать код. В обсуждении по ссылке как раз есть примеры, почему так сделать нормально не получится. Сравните опкоды `frozenset({1, 2})` и `frozenset((1, 2))`. В том же обсуждении есть примеры, почему.take_frozensetи.take_frozendictне могут быть единственными дешевыми конструкторами. Например: при многострочных выражениях - непонятно, какой тип будет у объекта до самого конца. И другие важные штуки.