Находки в опенсорсе
10.7K subscribers
11 photos
1 video
3 files
819 links
Привет!

Меня зовут Никита Соболев. Я занимаюсь опенсорс разработкой полный рабочий день.

Тут я рассказываю про #python, #c, опенсорс и тд.
Поддержать: https://boosty.to/sobolevn
РКН: https://vk.cc/cOzn36

Связь: @sobolev_nikita
Download Telegram
В Python3.14 добавили новую библиотеку для сжатия: Zstandard

- PEP: https://peps.python.org/pep-0784
- Документация: https://docs.python.org/3.14/library/compression.zstd.html
- Реализация: https://github.com/python/cpython/pull/133027

Существует такой новый алгоритм для сжатия: Zstandard c хорошим процентом сжатия и быстрым алгоритмом сжатия / разжатия. Его добавили в 3.14 как нативный модуль. И как раз заодно решили прибрать все другие алгоритмы в общий модуль compression.*

Теперь:
- compression.lzma отвечает за lzma
- compression.zstd за Zstandard
- compression.gzip за gzip и так далее

Пока данные новые модули просто делают re-export всех объектов из оригинальных модулей. Однако, в какой-то момент старые имена могут быть задеприкейчены.

> Any deprecation or removal of the existing modules is left to a future decision but will occur no sooner than 5 years from the acceptance of this PEP.

Как работает?

Сам алгоритм сжатия хорошо описан в соответствующем RFC. Его лучше почитать отдельно. Тем, кому такое интересно.

А мы поговорим про питоновскую часть.
Теперь питон зависит от новой опциональной библиотеки zstd.h, что будет, если ее нет? И тут мы должны познакомиться с системой конфигурации и сборки питона.

Мы используем AutoConf или .ac. Данный зверь – свой большой мир, который хочется как раз показать на примере. Спорим, вы не сможете с первого раза прочитать данную конструкцию?


dnl zstd 1.4.5 stabilised ZDICT_finalizeDictionary
PKG_CHECK_MODULES([LIBZSTD], [libzstd >= 1.4.5], [have_libzstd=yes], [
WITH_SAVE_ENV([
CPPFLAGS="$CPPFLAGS $LIBZSTD_CFLAGS"
CFLAGS="$CFLAGS $LIBZSTD_CFLAGS"
LIBS="$LIBS $LIBZSTD_LIBS"
AC_SEARCH_LIBS([ZDICT_finalizeDictionary], [zstd], [
AC_MSG_CHECKING([ZSTD_VERSION_NUMBER >= 1.4.5])
AC_COMPILE_IFELSE([
AC_LANG_PROGRAM([@%:@include "zstd.h"], [
#if ZSTD_VERSION_NUMBER < 10405
# error "zstd version is too old"
#endif
])
], [
AC_MSG_RESULT([yes])
AC_CHECK_HEADERS([zstd.h zdict.h], [have_libzstd=yes], [have_libzstd=no])
], [
AC_MSG_RESULT([no])
have_libzstd=no
])
], [have_libzstd=no])
AS_VAR_IF([have_libzstd], [yes], [
LIBZSTD_CFLAGS=${LIBZSTD_CFLAGS-""}
LIBZSTD_LIBS=${LIBZSTD_LIBS-"-lzstd"}
])
])
])


Самая важная часть: AC_CHECK_HEADERS([zstd.h zdict.h], [have_libzstd=yes], [have_libzstd=no]), она проверяет наличие нужных хедеров / зависимостей для компиляции. Данный код создает несколько проверок при генерации файла ./configure, которые позволяют проверить, есть ли такая библиотека на машине сборки.

Генерируется в результате вот такой трешняк: https://github.com/python/cpython/blob/30dde1eeb3fa1e0e7417f9cdded8fd90766f2559/configure#L22587-L22946

Данная мешанина из shell, C кода в строках и безумной обработки ошибок делает следующее:
- записывает файл с Сишным кодом
- вставляет туда нужные хедеры
- пытается скомпилировать
- если получилось, то пишет один конфиг
- если нет, то сборка понимает, что библиотеки нет, пишется другой конфиг
- на основе ./configure потом собирается правильный Makefile для компиляции самого питона

Вот тут мы указываем, какие части компилировать, если такая библиотека есть:


@MODULE__ZSTD_TRUE@_zstd _zstd/_zstdmodule.c _zstd/zstddict.c _zstd/compressor.c _zstd/decompressor.c


Посмотрим, как выглядит Makefile, если библиотека есть:


MODULE__ZSTD_STATE=yes
MODULE__ZSTD_CFLAGS=-I/opt/homebrew/opt/zstd/include
MODULE__ZSTD_LDFLAGS=-L/opt/homebrew/opt/zstd/lib -lzstd

Modules/_zstd/_zstdmodule.o: $(srcdir)/Modules/_zstd/_zstdmodule.c $(MODULE__ZSTD_DEPS) $(MODULE_DEPS_SHARED) $(PYTHON_HEADERS); $(CC) $(MODULE__ZSTD_CFLAGS) $(PY_STDMODULE_CFLAGS) $(CCSHARED) -c $(srcdir)/Modules/_zstd/_zstdmodule.c -o Modules/_zstd/_zstdmodule.o
# ...


А если такой библиотеки нет – то в итоговом Makefile просто не будет данной цели для сборки.

Обсуждение: как вам данный алгоритм сжатия? Как вам система сборки питона?
1👍79🔥2219🤯9😁4🤡2👌1🕊1
__code__.co_exceptiontable: начало

Чтобы познакомиться с таблицами обработки исключений, нам потребуется нырнуть глубоко.
Базовая идея: таблица исключений показывает, какие строки байткода покрыты обработчиками ошибок, а какие – нет. По сути – таблица неявных переходов между логическими лейблами. За счет данной технологии реализованы питоновские "zero-cost exceptions".

Возьмем для примера вот такую простую функцию:


def other(x, y):
res = None
try:
res = x / y
except ZeroDivisionError:
res = 0
finally:
print(res)


Получим вот такие: байткод (полная версия по ссылке) и таблицу исключений:


ExceptionTable:
L1 to L2 -> L3 [0]
L3 to L4 -> L6 [1] lasti
L4 to L5 -> L7 [0]
L5 to L6 -> L6 [1] lasti
L6 to L7 -> L7 [0]
L7 to L8 -> L8 [1] lasti


Что она делает?

Если на каком-то участке байткода возникает исключение, то мы прыгаем на нужную логическую метку из таблицы. Пример: в try (метки c L1 по L2 невключительно) прыгаем на L3.

У данной функции будет 3 интересных для нас куска байткода.
Первый – про обработку finally после успешного случая:


3 L1: LOAD_FAST_BORROW_LOAD_FAST_BORROW 1 (x, y)
BINARY_OP 11 (/)
STORE_FAST 2 (res)

7 L2: LOAD_GLOBAL 3 (print + NULL)
LOAD_FAST_BORROW 2 (res)
CALL 1
POP_TOP
LOAD_CONST 1 (None)
RETURN_VALUE


Тут мы просто делим два объекта и печатаем. Из интересного тут то, что finally разложился в набор байткода прямо после тела try. Нам не нужно никаких дополнительных манипуляций, чтобы управлять указателем на следующую инструкцию. Так происходит благодаря псевдо-инструкции SETUP_FINALLY. Так было не всегда, раньше тут был JUMP_FORWARD, а finally был общий для всех.

Второй – про вызов finally после обработанного ZeroDivisionError исключения:


-- L3: PUSH_EXC_INFO

4 LOAD_GLOBAL 0 (ZeroDivisionError)
CHECK_EXC_MATCH
POP_JUMP_IF_FALSE 6 (to L5)
NOT_TAKEN
POP_TOP

5 LOAD_SMALL_INT 0
STORE_FAST 2 (res)
L4: POP_EXCEPT
JUMP_BACKWARD_NO_INTERRUPT 28 (to L2)


Тут мы сравниваем тип ошибки, если она ZeroDivisionError, то обрабатываем, в конце обработки ошибки прыгаем к L2. Если ошибка другая, то прыгаем в L5 (будет ниже). Из интересного, POP_EXCEPT убирает текущее исключение из tstate->exc_info, так исключение считается обработанным.

И третий после необработанного исключения:


4 L5: RERAISE 0

-- L6: COPY 3
POP_EXCEPT
RERAISE 1
L7: PUSH_EXC_INFO

7 LOAD_GLOBAL 3 (print + NULL)
LOAD_FAST_CHECK 2 (res)
CALL 1
POP_TOP
RERAISE 0

-- L8: COPY 3
POP_EXCEPT
RERAISE 1


Здесь происходит самое интересное. Нам необходимо правильно обработать ошибки, которые могут случиться в except и finally блоках. Для такого у нас есть:
- L3 to L4 -> L6 [1] lasti для обработки ошибок в except
- L7 to L8 -> L8 [1] lasti для обработки ошибок в finally

Пример, как будут выполняться разные кейсы:
- other(1, 2): L1 -> L2 (finally)
- other(1, 0): L1 -> L3 -> L4 -> L2 (finally)
- other(1, 'a'): L1 -> L3 (TypeError) -> L5 -> L7 (finally) -> L8

Ссылки на исходники:
- RERAISE
- exception_unwind для поиска обработчика текущей ошибки
- get_exception_handler для разворачивания таблицы

Обсуждение: как часто вы пользуетесь finally в обработке сложных ошибок?

| Поддержать | YouTube | GitHub | Чат |
1🔥51🤯1312👍3🤔3🤡1
Находки в опенсорсе
Находки в опенсорсе: ty (red-knot) https://www.youtube.com/watch?v=5PCP4ICoirg Вышло видео про новый тайпчекер и lsp: ty (старое название red-knot) от авторов ruff и uv. Пока по первым впечатлениям – бомба! Не смотря на версию 0.0.0a8 🌚 Из плюсов: - Быстрый…
Находки в опенсорсе: pyrefly

https://youtube.com/watch?v=7TdxFGB6LKY

Еще одно видео про еще один новый тайпчекер для питона на расте!
Много их нынче стало.

В видео:
- Обсуждаем первую версию: pyre-check, обсудили taint analysis
- Сравниваем pyrefly с ty и mypy
- Смотрим на внутреннее устройство
- Применяем на реальном проекте

Ключевые ссылки из выпуска:
– Доклад о pyrefly на PyCon: https://youtu.be/ZTSZ1OCUaeQ?si=s_DPOOzsdeTk5Uqo
– pyrefly vs ty: https://blog.edward-li.com/tech/comparing-pyrefly-vs-ty (сильно советую!)

Вывод: пока очень сырой, много багов, но быстрый. Ключевой вывод: отлично, что есть конкуренция
26🔥439🤡5👍4
PEP-734: Subinterpreters in stdlib

- PEP: https://peps.python.org/pep-0734
- Обсуждение: https://discuss.python.org/t/pep-734-multiple-interpreters-in-the-stdlib/41147
- Документация: https://docs.python.org/3.14/library/concurrent.interpreters.html

Что оно такое?

Несколько полноценных интерпретаторов работающих рядом. Какие плюсы?
- Один процесс
- Один тред, но руками можно создавать еще
- Простые данные можно шарить без необходимости pickle, сложные нужно пиклить
- По GILу на интерпретатор, все еще можно получить плюшки настоящей многозадачности по сети
- Работает с asyncio

Минусы:
- C код нужно было значительно переработать, не все C расширения поддерживаются (пока)

Получается хорошая универсальность для разных задач.

Немного истории

Есть несколько важных нетехнических аспектов про процесс создания данной фичи:
- PEP-734 и Free-Threading делают очень похожие вещи – позволяют реализовывать настоящую многозадачность, но разными способами
- Изначально субинтерпретаторы появились в 3.10 в виде только C-шного АПИ
- Есть отдельный PyPI пакет с данным кодом
- Пайтон часть в виде PEP-734 был добавлена в 3.14 уже после feature freeze
- Изначально планировалось добавить его как модуль interpreters, однако в последний момент он стал concurrent.interpreters, вот тут доступно большое обсуждение

Как работает?

Внутри довольно много разных C-шных модулей:
- Основа: https://github.com/python/cpython/blob/main/Python/crossinterp.c
- Дефиниция модуля: https://github.com/python/cpython/blob/main/Modules/_interpretersmodule.c
- Очередь для обмена сообщениями между интерпретаторами: https://github.com/python/cpython/blob/main/Modules/_interpqueuesmodule.c
- Набор примитивов: https://github.com/python/cpython/blob/main/Modules/_interpchannelsmodule.c

Но, для пользователей - важен только питоновский АПИ, что прекрасно. Он получился простым и понятным:


interp = interpreters.create()
try:
interp.exec('print("Hello from PEP-554")')
finally:
interp.close()


Давайте посмотрим на пример и замерим!

Тестирую на M2Pro 2023. Полный код тут.
Код, CPU-bound задача, считаем факториалы от числа до числа:


def worker_cpu(arg: tuple[int, int]):
start, end = arg
fact = 1
for i in range(start, end + 1):
fact *= i


Будем разбивать работу на 4 части, считать факториал первых 10000, потом вторых и тд. Вот так запускаем сабинтерпретаторы:


from concurrent.futures import InterpreterPoolExecutor

def bench_subinterpreters():
with InterpreterPoolExecutor(CPUS) as executor:
list(executor.map(worker, WORKLOADS))


Аналогично запускаем и треды с процессами.

Результаты:


Regular: Mean +- std dev: 163 ms +- 1 ms
Threading with GIL: Mean +- std dev: 168 ms +- 2 ms
Threading NoGIL: Mean +- std dev: 48.7 ms +- 0.6 ms
Multiprocessing: Mean +- std dev: 73.4 ms +- 1.5 ms
Subinterpreters: Mean +- std dev: 44.8 ms +- 0.5 ms


Субинтерпретаторы значительно ускорили данный пример. Работает даже быстрее FT 😱

А теперь для IO-bound задачи возьмем такой пример:


def worker_io(arg: tuple[int, int]):
start, end = arg
with httpx.Client() as client:
for i in range(start, end + 1):
client.get(f'http://jsonplaceholder.typicode.com/posts/{i}')


И снова concurrent.interpreters показывают хорошее время:


Regular: Mean +- std dev: 1.45 sec +- 0.03 sec
Threading with GIL: Mean +- std dev: 384 ms +- 17 ms (~1/4 от 1.45s)
Threading NoGIL: Mean +- std dev: 373 ms +- 20 ms
Multiprocessing: Mean +- std dev: 687 ms +- 32 ms
Subinterpreters: Mean +- std dev: 547 ms +- 13 ms


Тут может показаться, что как-то не очень много перформанса у нас получилось. Но! Вспоминаем, что внутри можно создавать дополнительные треды, чтобы еще ускорить работу. А можно даже и asyncio так параллелить, хотя я пока и не пробовал.

Обсуждение: как вам фича?

| Поддержать | YouTube | GitHub | Чат |
53👍7821🔥13🤡4🤔2
Анонс FishITStream

(да, аллюзия на FastStream)

Мы с пацанами решили, что если и делать стримы по программированию, то сразу стримы с рыбалки. Потому что нет ничего лучше, чем говорить про программирование с рюмкой чая да на природе, глядя на озерцо.

Кто будет на связи:
Коля Хитров — Python-блогер и серийный спикер
Никита Пастухов — автор FastStream, галерный гребец, филантроп
Роман Пожарнов — автор asgi-monitor, спортивный рыбак, говорит на рыбьем языке
– ну и я, конечно

О чем поговорим:
– Развитие языка Python: как развивается язык, и почему Python всё ещё лучше Go!
– Как сделать стрим из дикой природы
– Конференции и нетворкинг: зачем идти слушать и выступать, как найти тему для доклада
– Какую прикормку лучше всего брать на карася
– И, конечно же, мы не оставим без внимания OpenSource: обсудим развитие продуктов, успешные проекты, мотивацию людей и секреты правильного использования OSS
– А еще всякое про карьеру и прочее, в чем я не разбираюсь 🌚

Время: 29 июня, 12:00 МСК
Место: https://www.youtube.com/watch?v=j-XAjIlCRGg

Кстати, стрим будет на канале Коли, туда можно смело подписываться!

Надеюсь, что все получится технически.
2🔥10919👍13🤡4😢3😁2🕊2🎉1
Находки в опенсорсе
PEP-734: Subinterpreters in stdlib - PEP: https://peps.python.org/pep-0734 - Обсуждение: https://discuss.python.org/t/pep-734-multiple-interpreters-in-the-stdlib/41147 - Документация: https://docs.python.org/3.14/library/concurrent.interpreters.html Что…
PEP-734: subinterpreters и _interpqueues

Еще одной важной часть субинтерпретаторов являются очереди. Прочитать про них можно тут:
- https://peps.python.org/pep-0734/#queue-objects
- https://github.com/python/cpython/blob/main/Lib/concurrent/interpreters/_queues.py
- https://github.com/python/cpython/blob/main/Modules/_interpqueuesmodule.c

Зачем нужны очереди? Чтобы передавать объекты между интерпретаторами:


>>> from concurrent import interpreters

>>> interp = interpreters.create()
>>> queue = interpreters.create_queue()

>>> complex_object = [1, 'a', {'key': 'val'}, (2, 3), None, object()]
>>> complex_object # notice original id
[1, 'a', {'key': 'val'}, (2, 3), None, <object object at 0x101169ff0>]

>>> queue.put_nowait(complex_object)
>>> interp.prepare_main(queue=queue) # passing `queue` object to other interpreter

>>> interp.exec('print(queue.get_nowait())') # notice id change
[1, 'a', {'key': 'val'}, (2, 3), None, <object object at 0x101b054e0>]


В чем важные особенности?
- Не все объекты переданные между очередями копируются. Например, immortal objects - не копируются:


>>> id(12)
4308538240
>>> interp.exec('print(id(12))')
4308538240


- memoryview (как и другие PyBuffer объекты) имеет особые гарантии. Мы передаем память прям физически. Без копирования. Потому что np.array и подобное - тоже буферы. Если бы мы копировали память буферов, то такие вычисления не отличались бы от multiprocessing, который часть по памяти имеет проблемы. Здесь – все по-другому:


>>> b = bytearray(b'123')
>>> m = memoryview(b)

>>> queue.put_nowait(m)
>>> interp.exec('(m := queue.get_nowait()); print(m); m[:] = b"456"') # changing memory directly
<memory at 0x103274940>

>>> b # was changed in another interpreter!
bytearray(b'456')


Будьте осторожны с мутабельными буферами. Тут нужно понимать, что ты делаешь. Инструмент - очень мощный.
Больше подробностей будет в моем интервью с автором субинтерпретаторов.

- Остальные объекты копируются через pickle (пока что). В планах – ускорить и упростить данный процесс:


# ex.py
>>> class Custom:
... def __init__(self, arg: int) -> None:
... self.arg = arg
... def __getstate__(self):
... print('__getstate__')
... return {'arg': self.arg}
... def __setstate__(self, state):
... print('__setstate__', state)
... self.arg = state['arg']

# main.py
>>> from ex import Custom
>>> c = Custom(1)
>>> c
<ex.Custom object at 0x101a078c0>

>>> queue.put_nowait(c)
__getstate__

>>> interp.exec('print(queue.get_nowait())') # different object
__setstate__ {'arg': 1}
<ex.Custom object at 0x103222f20>


А еще есть каналы! CSP, прям как в Go: https://peps.python.org/pep-0554/#channels
Но, они пока готовы сильно меньше, чем очереди, которые уже достаточно полезные.

Но, самое главное: можно делать свои модули на C, которые будут копировать данные так, как вам нужно (или не копировать их вовсе). АПИ готовы, они работают.

Круто?

Минутка монетизации опенсорса

Часовое интервью на английском с русскими субтитрами с Eric Snow – автором субинтерпретаторов – уже на бусти. Дополнительно я вставил все примеры кода и отсылки на ПЕПы прямо в видео, чтобы было удобно смотреть и понимать, о чем мы говорим. Через неделю оно будет доступно для всех на ютюбе.

Если вы хотите поддерживать глубокий бесплатный технический контент, уникальных гостей на интервью, а так же другую мою бесплатную работу: от опенсорса до статей здесь и на хабре – есть бусти!

А еще я буду там выкладывать список ссылок на весь другой контент, который я делаю. В одном месте. Удобно!

Обсуждение: какие у вас есть варианты использования субинтерпретаторов?

| Поддержать | YouTube | GitHub | Чат |
111🔥4318👍13🤡3🤯2
Находки в опенсорсе
PEP-734: subinterpreters и _interpqueues Еще одной важной часть субинтерпретаторов являются очереди. Прочитать про них можно тут: - https://peps.python.org/pep-0734/#queue-objects - https://github.com/python/cpython/blob/main/Lib/concurrent/interpreters/_queues.py…
Лучший курс по Python 15: Subinterpreters

Продолжаем говорить про субинтерпретаторы.
Пригласил их автора – Eric Snow – чтобы поговорить про историю, актуальное состояние проекта и его будущее. Особо отмечу темы про будущие оптимизации. Как ускорить старт интерпретатора? Как сделать возможным шеринг большего количества данных?

Все здесь! Есть русские и английские субтитры.
А еще я добавил много примеров кода и ссылки на исходники / пепы / статьи.
Жмите на паузу и читайте :)

https://www.youtube.com/watch?v=VBiaNNpLzWA

Внутри:
00:00 Вступление
00:11 Представление гостя
01:20 Введение в subinterpreters и PEP-554
04:21 Как subinterpreters выглядят с точки зрения OS?
05:50 Зачем добавили субинтерпретаторы в Python1.5?
08:01 Сколько субинтерпретаторов можно запустить в один момент?
09:19 История subinterpreters
17:25 Изоляция модулей и PEP-687
25:22 Immortal objects и PEP-683
29:15 Static Types
32:50 Проблема с модулем SSL
34:44 Связь subinterpreters и free-threading
42:45 Erlang и Actor Model
43:50 CSP, Channels
45:23 _interpqueues
46:11 (не)Копирование данных при отправке данных в очередь
48:53 Можно ли безопасно делить все буфферы? memoryview
49:53 subinterpreters vs multiprocessing
53:09 subinterpreters and asyncio
56:07 PEP-734
56:37 Сборщик мусора, GC
58:13 Как сделать еще быстрее и лучше в будушем?
01:03:34 Какие библиотеки стоит сделать сообществу?
01:08:14 Завершение

Обсуждение: кого бы вы хотели видеть в качестве будущего гостя?

| Поддержать | YouTube | GitHub | Чат |
19🔥13621👍19💩2🤡2🥰1
PEP-797: Иммортализация произвольных объектов

- Ссылка на черновик PEP: https://github.com/python/peps/pull/4497
- Оригинальный PEP-683 с Immortal объектами: https://peps.python.org/pep-0683

Продолжаем про будущее субинтерпретаторов! Да, что-то меня увлекла данная тема :)

Как делиться объектами без копирования данных в субинтерпретаторах?

Чтобы легко делиться объектами между субинтерпретаторами, необходимо, чтобы они были полностью иммутабельными. Полностью. Не с точки зрения питона, а с точки зрения C.

Даже если вы будете делиться объектами, которые внешне нельзя изменить, то все еще будет один очень важный нюанс.

Напомню, что объект питона с точки зрения C выглядит так (упрощенно):


struct PyObject {
Py_ssize_t ob_refcnt;
PyTypeObject *ob_type;
};


И вот тот самый ob_refcnt все время меняется. Буквально в любом C коде все время вызовы Py_DECREF и Py_INCREF. Без изменения счетчика ссылок невозможно выполнить никакой код. И буквально все части питона содержат вызовы, которые уменьшают или увеличивают счетчик ссылок во время работы программы.

Следовательно, мы можем словить гонку данных.
Чтобы гонки не было, мы не должны менять ob_refcnt. А чтобы его не менять, нам нужно, чтобы объект был бессмертным: жил на протяжении всего времени жизни программы и потом был правильно собран.

Что такое вообще Immortal объекты?

Ровно как я и писал выше – объекты, которые не участвуют в подсчете ссылок и живут, пока программа не завершится. Что-то вроде static в C и 'static в Rust.
Внутри питона есть несколько мест, которые нам интересны с данной точки зрения:


#define _Py_IMMORTAL_REFCNT_LOCAL UINT32_MAX


То есть – объект Immortal, когда у него просто очень большой refcnt. Что легко проверить:


>>> import sys
>>> sys.getrefcount(None)
3221225472

>>> x = None # он не будет меняться при создании новых имен:
>>> sys.getrefcount(None)
3221225472


И вот почему:


static inline Py_ALWAYS_INLINE void Py_INCREF(PyObject *op)
{
if (_Py_IsImmortal(op)) {
_Py_INCREF_IMMORTAL_STAT_INC();
return;
}
op->ob_refcnt++;
}

static inline Py_ALWAYS_INLINE void Py_DECREF(PyObject *op)
{
if (_Py_IsImmortal(op)) {
return;
}
if (--op->ob_refcnt == 0) {
_Py_Dealloc(op);
}
}


Когда объект "immortal", то с ним и не нужно проводить никаких доп операций.

Как предлагается делать свои?


import sys

admin = User('admin')
sys._immortalize(admin)
assert sys._is_immortal(admin)


Все! Теперь объект admin будет жить до конца программы. И его можно будет использовать в разных субинтерпретаторах без копирования:


>>> from concurrent import interpreters
>>> interp = interpreters.create()
>>> interp.prepare_main(admin=admin)
>>> interp.exec('print(admin)')
'<User: admin>'


Поверх такого АПИ, скорее всего, будет какое-то красивое АПИ. Нужно будет следить, чтобы бессмертные объекты не были слишком большими / их не было слишком много. Ну и подготовить их логически тоже необходимо.
Итого: иммутабельные объекты, субинтерпретаторы, полная параллельность. Да у нас, что, Erlang?!

Обсуждение: как вам данная фича?

| Поддержать | YouTube | GitHub | Чат |
160👍32🔥11🤔6💩3🕊3🤡3😁1🤯1
Снимок экрана 2025-07-25 в 16.38.01.png
454.8 KB
Делаем бесплатный курс по vscode?

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

Тем более видосы с нарезкой моего подкаста на данную тему с @t0digital собрали много обсуждений и даже возмущений. А значит – тема горячая :)

Будем делать из второй картинки третью.

О чем поговорим?
- Почему DX важен?
- Почему vscode, а не vim / pycharm / emacs / тд. И как применить такие же подходы к другим средам
- О минимализме. Для успешной работы вам нужно меньше инструментов, а не больше
- О том, как сделать минимальное количество полезных горячих клавиш, которыми вы реально будете пользоваться
- Как навигироваться по коду, файлам, важным местам в проекте
- Какие принципы позволят вам сделать свой уникальный рабочий сетап, который удобен вам
- Как можно делать свои крутые инструменты, как пример для работы со сложными кейсами в git: https://github.com/sobolevn/fzf-simple-git
- Как писать свои темы, плагины. И когда их не писать

Будет крайне полезно, чтобы писать код быстрее и проще.

Мои конфиги за ~10 лет работы всегда можно посмотреть тут: https://github.com/sobolevn/dotfiles

Собираем донат goal на +16 человек – и начинаем! Все будет бесплатно и на ютюбе. Подписка на https://boosty.to/sobolevn стартует со 100 рублей.

Холивар про IDE объявляется открытым в комментах 🌚
53👍25754🔥47👎9🤡4💩3👏2😱1👌1🕊1
Находки в опенсорсе
Анонс FishITStream (да, аллюзия на FastStream) Мы с пацанами решили, что если и делать стримы по программированию, то сразу стримы с рыбалки. Потому что нет ничего лучше, чем говорить про программирование с рюмкой чая да на природе, глядя на озерцо. Кто…
Находки на рыбалке: FastStream

https://www.youtube.com/watch?v=Qf4r8xV-YNk

Сгоняли с @fastnewsdev на рыбалку: поговорили про FastStream, пожарили мясо, обсудили проблемы разработчиков при работе с очередями / брокерами сообщений.

В своем первом посте мы уже знакомились с базовыми фичами FastStream'а, но для тех, кто пропустил, напоминаю: FastStream – аналог FastAPI, но для работы с событиями в брокерах / очередях.

Пример:


from faststream import FastStream
from faststream.kafka import KafkaBroker

broker = KafkaBroker("localhost:9092")
app = FastStream(broker)

@broker.publisher("another-topic")
@broker.subscriber("first-topic")
async def handle(user: str) -> str:
return f"Hi, {user}!"


Что делает данный код? Читает сообщения из first-topic, парсит из них поле user типа str, выполняет логику обработки, отправляет новое сообщение в another-topic. Просто? Удобно?

Что нам дает такой код?
- Декларативное описание, чего мы хотим. Не надо руками создавать коннекты и рулить потоком выполнения
- AsyncAPI документацию (аналог OpenAPI в вебе)
- Удобное тестирование
- Кучу других плюшек!

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

Внутри видео обсудили:
- Детали работы DI фастстрима
- Встроенное Observability
- Open Tracing
- Сообщество фреймворка (тут не будет проблемы "одного автора", сообщество живет!)
- Отличия от Celery: когда брать что?

Репозиторий: https://github.com/ag2ai/faststream
Документация: https://faststream.ag2.ai

Обсуждение: используется ли в ваших проектах асинхронная архитектура? Как вы работаете с событиями?

| Поддержать | YouTube | GitHub | Чат |
570👍36🔥16🤡5💩3🥰1
Находки в опенсорсе
Снимок экрана 2025-07-25 в 16.38.01.png
minimal vscode: открываем окна

https://www.youtube.com/watch?v=frZkPK_1Ui4

Нет, не от духоты, ее в видео как раз не будет 🌚️️️️
Видео короткое, динамичное, практичное.

Перед тем как учиться пользоваться vscode, необходимо:
1. Её поставить
2. Научиться её открывать
3. Располагать её на рабочем пространстве

В видео поговорили про:
- Brewfile и синхронизацию программ / плагинов
- Hotkey managers на примере https://github.com/koekeishiya/skhd
- Тайловые менеджеры окон: https://github.com/rxhanson/Rectangle
- Красивости вроде https://topnotch.app и https://hazeover.com

Все материалы для всех операционных систем тут: https://github.com/sobolevn/the-best-python-course/blob/main/minimal_vscode/links/1-open-vscode.md

Большое спасибо за такой отклик и поддержку 🧡, видео про отключение лишних панелей навигации уже в работе. Скоро будет!

Обсуждение: какие тайловые менеджеры используете вы?

| Поддержать | YouTube | GitHub | Чат |
17117👍36🔥21💩5🤡4🎉1🕊1
minimal vscode: убираем Activity и Side Bars

https://www.youtube.com/watch?v=wxbifNb1Q1o

Одни из главных потребителей места на мониторе: Activity Bar и Side Bar.
Их настолько просто убрать, чтобы оставалось больше места для кода, что я не вижу причин НЕ делать так.

- В несколько хоткеев сделаем столько же (и даже больше, учитывая новый отдельный Outline View)
- А еще отцентрируем Command Palette, чтобы было удобнее
- Покажу, почему Side Bar лучше иметь справа, а не слева от кода
- Прячем Side Bar, когда он нам не нужен (почти всегда)
- Покажу своих котов 🐈

Короткое, но максимально содержательное видео.

А что дальше по контенту?

На неделе будет большой пост из цикла "отвечаем на любимые вопросы с собесов": поговорим про то, как создаются объекты. PyObject_New, PyObject_NewVar, аллокаторы, изменяемость и неизменяемость. Поговорим: чем мутабельность int отличается от мутабельности tuple и list. Спрашивали ли вас такое? Считаете ли вы такой вопрос вообще полезным?

А еще скоро будут видео с прошлого митапа PythoNN! Три очень крутых доклада.

Следом будет продолжение "Лучшего курса по Питону" – устройство memoryview, полезнейший тип данных в питоне, который незаслуженно часто забывают.

Потом вернемся к чистке vscode от ненужного.

Если нравится такой план, не забудь поддержать :)

Обсуждение: пользуетесь ли вы Activity / Side Bar? Какие у вас самые популярные варианты использования данных инструментов?

| Поддержать | YouTube | GitHub | Чат |
1🔥82👍2211💩4🤡4
Используем ИИ для ревью кода

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

Ребята из GitVerse не так давно выпустили данную фичу, она доступна для всех. Я воспользовался возможностью и потестил.

Документация: https://gitverse.ru/docs/ai-agents/gigacode-agent

Методология проверки

Полный репозиторий: https://gitverse.ru/sobolevn/ai-code-review

Я придумал много разных типов ошибок и написал специально 🌚 плохой код на питоне. Вот он: https://gitverse.ru/sobolevn/ai-code-review/content/master/ai_code_review/example.py

- Небезопасный Python код: subprocess.run([user_input, ...])
- Плохое имя переменной: moy_lubimiy_film = '{"album_id": 1, "next_song": 2}'
- Сложный Python код в функции complex_code
- Затирание встроенного в Python имени: input
- Использование Any без явной причины: input: Any
- Неправильная аннотация типа данных: x: int = 'a'
- Логическая ошибка: if subvalue in seen
- Использование несуществующей переменной: missing_var
- Плохо отформатированный Python код: my_unformatted_list
- Некорректный порядок импортов: from typing import Any
- Небезопасная и устаревшая зависимость django@1.9 в pyproject.toml

Далее, я проверил, можно ли вообще статически найти ошибки, которые я заложил?
Я сделал GitVerse Actions файл, который запускает mypy, wemake-python-styleguide, ruff, safety.
И удостоверился, что они могут найти все ошибки, кроме одной проблемы из двух самых сложных во всем программировании:
- Плохое имя переменной: moy_lubimiy_film = '{"album_id": 1, "next_song": 2}'

Данная ошибка будет baseline для нашего ИИ.

Ревью при помощи ИИ

Чтобы ревью случилось, нужно просто добавить giga-code-agent в качестве ревьюера.
Что он делает?
1. Делает описание PR, если его нет. Там кратко перечисляет тип PR, ключевые изменения, важные технические детали. Удобно! Часто такие PRы просто раньше были пустые
2. Делает ревью. Описывает найденные проблемы. Учитывает тесты, которые отвечают за изменения. Действительно находит многие проблемы. От типизации до оформления. В текстах README.md нашел несколько ошибок. Но! Очень важно: находит не все ошибки
3. Делает советы по изменениям. Пока они оставляют желать лучшего. Но изменения по форматированию - я бы принял, остальные - нет
4. Можно уточнять что-то при помощи команды /ask

Итог: найдено 6 (или 3 в зависимости от способа запуска) ошибок. Что уважаемо, учитывая, что было 0 конфигурации, 0 интеграций. Надеюсь, дальше будет еще лучше :)

Наша baseline ошибка, кстати, была найдена, когда я явно задал контекст.

Вывод: используйте ИИ ревью вместе с другими инструментами статического анализа.
Полный отчет со всеми подробностями по ссылке.

Обсуждение: доверили бы вы ревьюить свой код ИИ?
👍79🤡4720🔥15🤔5😁3💩3🕊2🤯1
Большая бесплатная Python конференция в Нижнем Новгороде!

У меня хорошая новость. Делаем бесплатную конфу по питону, пригласили топовых специалистов: core-разработчиков, контрибьюторов, организаторов разных комьюнити движух, специалистов из индустрии. И все – участники нашего чата. Как же так получилось 🌚

Вот такой список докладов:

- Подопригора Кирилл, CPython core dev, Evrone: Кто такой CFG в CPython, и какую роль он выполняет?
- Хитров Николай, организатор @peer_2beer, Точка Банк: Проектирование — это когда чувствуешь, а не какие-то там циферки
- Ильиных Илья, организатор @spbpythonnews, блоггер @kydavoiti: Vim — это метаредактирование
- Джалаев Давид, Газпром-нефть ЦР: Continuous profiling
- Порядин Алексей, участник @pytho_nn: AI-агенты в каждый дом
- Мирянов Сергей, CPython contributor, РН-Технологии: Внутреннее устройство сборки мусора в CPython 3.14+
- Неретин Степан, CPython contributor, Postgres Professional: Своя файловая система за 5 минут на Python
- Ефимов Михаил, CPython contributor, X5 Tech: Генератор байткода и байткод генератора

Ну мощь же 💪
Да, конференции можно делать и не за 50 тыщ за билет.

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

Бронируйте дату! Если давно хотели побывать в Нижнем – вот идеальный повод. Буду рад всех видеть лично!

📍 Когда: 20 сентября 2025 года
📍 Где: ул. Нижне-Волжская набережная, 11, «Академия Маяк» им. А.Д. Сахарова

Регистрация обязательна: https://dev.itgorky.ru

| Поддержать | YouTube | GitHub | Чат |
10🔥136👍2112💩3
Три типа объектов в Питоне

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

PyObject

Все мы знаем, что в питоне все объект или PyObject *, который упрощенно выглядит так (в FT сборке он посложнее):


struct _object {
Py_ssize_t ob_refcnt;
PyTypeObject *ob_type;
}


То есть: у нас есть только счетчик ссылок на объект и указатель на его тип. Первое меняется очень часто, если объект не immortal. Второе тоже можно менять в некоторых случаях:


>>> class A:
... __slots__ = ()

>>> class B:
... __slots__ = ()

>>> a = A()
>>> type(a)
<class '__main__.A'>
>>> a.__class__ = B
>>> type(a)
<class '__main__.B'>


Получается, что большинство объектов мутабельные уже на данном уровне.

Но, в целом есть три типа объектов, разные по уровню мутабельности:
1. Такие как None: ob_refcnt не меняется (immortal), тип менять нельзя, ведь Py_TPFLAGS_IMMUTABLETYPE установлен (static type), размер неизменный 0 для всех потенциальных значений
2. Такие как int: ob_refcnt может меняться для больших чисел (маленькие инты - immortal), тип менять нельзя, размер нельзя менять, но он будет разный для разных чисел:


>>> sys.getsizeof(1)
28
>>> sys.getsizeof(10000000000000)
32


3. Такие как list: ob_refcnt всегда меняется, тип менять нельзя, размер меняется

Отдельно нужно отметить, что пользовательские классы обычно еще более мутабельны, потому что их тип менять можно.
Но, вопрос в другом: а где вообще хранится размер объекта и его внутренности? Раз в PyObject ничего такого нет.

C-API

В C-API питона есть два полезных макроса: PyObject_HEAD для объектов фиксированного размера и PyObject_VAR_HEAD для объектов, которые могут менять размер.


struct PyVarObject {
PyObject ob_base;
Py_ssize_t ob_size; /* Number of items in variable part */
};

#define PyObject_HEAD PyObject ob_base;
#define PyObject_VAR_HEAD PyVarObject ob_base;


Хотим поменять размер объекта? Увеличиваем ob_size, аллоцируем новую память (если нужно) под новые объекты внутри.

Итоговые объекты используют примерно такую логику:


typedef struct {
PyObject_VAR_HEAD
/* Vector of pointers to list elements. list[0] is ob_item[0], etc. */
PyObject **ob_item;

/* ob_item contains space for 'allocated' elements. The number
* currently in use is ob_size.
*/
Py_ssize_t allocated;
} PyListObject;


То есть: на самом деле все объекты list (и многие другие) не просто PyObject, они:
- Имеют свой собственный тип: PyListObject
- Имеют общую абстракцию для размерности: PyVarObject
- Имеют общую абстракцию для типа и счетчика ссылок: PyObject

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

Итог

1. None не имеет внутреннего состояния вообще (не использует ничего)
2. int может иметь разный размер, но не может изменяться, потому использует PyObject_HEAD (раньше был PyObject_VAR_HEAD, там сложная история):


typedef struct _PyLongValue {
uintptr_t lv_tag; /* Number of digits, sign and flags */
digit ob_digit[1];
} _PyLongValue;

struct _longobject {
PyObject_HEAD
_PyLongValue long_value;
};


3. list может иметь разный размер и может изменятся, потому использует PyObject_VAR_HEAD, как я показывал выше

Обсуждение: как вы думаете, как работает len() для list?

| Поддержать | YouTube | GitHub | Чат |
6👍7522🔥4💩4🕊1
Находки в опенсорсе
minimal vscode: убираем Activity и Side Bars https://www.youtube.com/watch?v=wxbifNb1Q1o Одни из главных потребителей места на мониторе: Activity Bar и Side Bar. Их настолько просто убрать, чтобы оставалось больше места для кода, что я не вижу причин НЕ…
minimal vscode: убираем вкладки

https://www.youtube.com/watch?v=reT_wnDSaX4

Вкладки любят делать вид, что они очень полезны. Однако, такое впечатление обманчиво. Навигация по ним будет съедать у вас кучу времени. Взамен – есть способы лучше.

Что будет в видео?

- Как убрать вкладки?
- Как сделать заголовок вкладки читаемым?
- Что использовать вместо вкладок?
- Какие способы навигации удобнее вкладок?

Пельмени

Пельмени? Пельмени!

А еще в середине видео я решил попиарить свои любимые пельмени из родного Новосибирска. А мы там знаем толк в пельменях. Если вы не сидите в нашем чате, то вы пропустили все мемы про пельмени, и откуда вообще такая тема пошла :)

Сделал для вас набор рецептов для их приготовления от самого простого уровня до довольно замороченного: github.com/sobolevn/the-best-python-course/tree/main/minimal_vscode/dumplings

Рецепты написаны на языке Cooklang: https://cooklang.org
Специальный язык для разметки рецептов + среда для рендеринга рецептов в терминале и в виде сайтика.
Очень прикольная штука, выглядит вот так:


Положите пельмени в воду, варите пока не всплывут все пельмени и еще ~{7%min} после.
Главное – не переварить, блин!

При подаче смешайте @кетчуп и @майонез в качестве соуса.


Теперь у меня на гитхабе есть репозиторий, в котором в один момент времени есть:
- вызов syscall'ов на asm из питона
- рецепты пельменей с кетчунезом

Кстати про Новосибирск

Тут ребята организуют первый Python митап в Новосибирске: https://xn--r1a.website/python_in_nsk/698
Если вы живете где-то рядом, то присоединяйтесь: @python_in_nsk

Обсуждение: а как вы любите готовить ваши пельмени? 🥟

| Поддержать | YouTube | GitHub | Чат |
7771🔥36👍10😁8💩7🤡7
`__typing_subst__` – магический метод в Python, про который вы не слышали

В питоне сложилась довольно сложная ситуация с типизацией. В отличии от многих других языков, у нас нет специального "мини-языка типов". Что сильно упрощает работу с типизацией для авторов языка. Ведь им не нужно поддерживать поведение объектов типизации в рантайме.

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

Скажем, у нас есть TypeAliasType объект с одной типовой переменной. Мы хотим её явно указать:


type StrDict[_V] = dict[str, _V]

numbers: StrDict[int] = {'one': 1}


Тут все довольно просто в рантайме: StrDict[int] вызовет __getitem__ вот тут. Кстати, там Cшный код, конечно же. И нам просто вернется нужный GenericAlias объект с нужными __args__:


>>> type StrDict[_V] = dict[str, _V]
>>> StrDict[int], type(StrDict[int]), StrDict[int].__args__
(StrDict[int], <class 'types.GenericAlias'>, (<class 'int'>,))


Мы видим, что замена _V на int работает! Пока что 🌚

А как физически происходит замена переменной?

Начнем с того, что в Python есть 3 TypeVar-like объекта: TypeVar, TypeVarTuple, ParamSpec. У каждого из них есть свои правила, как можно их заменять (в некоторых контекстах):
- TypeVar можно заменять на одно другое типовое значение
- TypeVarTuple можно заменять на любое количество типовых значений
- ParamSpec можно заменять на Concatenate, ... и список типов

Пример ошибки замены, исходник:


>>> from collections.abc import Callable
>>> from typing import ParamSpec, TypeVar
>>> P = ParamSpec('P')
>>> R = TypeVar('R')

>>> Callable[P, R][0, int]
Traceback (most recent call last):
TypeError: Expected a list of types, an ellipsis, ParamSpec, or Concatenate. Got <class 'int'>


Пытаемся заменить P на 0, что не является корректной заменой.
Но как фактически проверить, что замена корректная? И вот тут входит в дело __typing_subst__.

Он определен у всех типов TypeVar-like. Мы посмотрим на ParamSpec. Все, конечно же, написано на дикой смеси C и Python. И Cшная реализация ParamSpec.__typing_subst__ вызывает typing._paramspec_subst. Почему так? В 3.12 все переписали на C в спешке. Но некоторые части были слишком сложны, их оставили на питоне.

Аналогично:


>>> P.__typing_subst__([])
()
>>> P.__typing_subst__([int, str])
(<class 'int'>, <class 'str'>)
>>> P.__typing_subst__(...)
Ellipsis
>>> P.__typing_subst__(0)
Traceback (most recent call last):
TypeError: Expected a list of types, an ellipsis, ParamSpec, or Concatenate. Got 0


Подготовка к замене

Но, все еще чуть-чуть сложнее. Потому что перед тем как что-то заменить, нам нужно подготовиться к замене.
Есть второй магический метод, исходники: __typing_prepare_subst__. Он нужен, чтобы собрать аргументы для замены. Потому что у нас, например, могут быть неявные аргументы с default. Проверим на TypeVarTuple:


>>> class Custom[T1, *Ts=(int, int)]: ...
...
>>> Custom[str]
__main__.Custom[str, [int, int]]
>>> Custom[str, str]
__main__.Custom[str, str]


Ну или напрямую:


>>> Custom.__type_params__[1].__typing_prepare_subst__(*Custom.__type_params__[1], ())
([(<class 'int'>, <class 'int'>)],)

>>> Custom.__type_params__[1].__typing_prepare_subst__(*Custom.__type_params__[1], (str,))
((<class 'str'>,),)


Здесь в первом случае [int, int] нам как раз добавили в __args__ через __typing_prepare_subst__ вот тут.

Вот такая машинерия выполняется каждый раз, когда нам нужен Generic с параметрами. Потому с 3.14 все аннотации будут ленивыми по-умолчанию. А __annotate__ будет выполняться только тогда, когда аннотации будут запрашивать реально для рантайма.

Обсуждение: задумывались ли вы о работе аннотаций типов в рантайме? Сталкивались ли с проблемами в данной сфере? Тормозило?

| Поддержать | YouTube | GitHub | Чат |
6🤯5218🔥14👍5💩2👏1🤔1🕊1
Находки в опенсорсе: EasyP – тулбокс для ProtoBuf файлов

https://www.youtube.com/watch?v=XI-dNpM77iM

easyp – пакетный менеджер, билд-система и линтер для .proto файлов.
Хоть easyp и написан на #go 😱, одна из его фишек в том – что вы можете использовать любые плагины для генерации финального кода: он может быть хоть на #python, хоть на #rust.

Если много используете ProtoBuf – обязательно для ознакомления!

Как оно работает?


# Секция для правил линтера:
lint:
use:
- DEFAULT

# Секция с зависимостями:
deps:
- github.com/googleapis/googleapis
- github.com/grpc-ecosystem/grpc-gateway@v2.20.0

# Секция для правил сборки и генерации итоговых файлов:
generate:
plugins:
- name: go
out: .
opts:
paths: source_relative
- name: go-grpc
out: .
opts:
paths: source_relative
require_unimplemented_servers: false


Прощайте огромные Makefile с кучей скриптов для сборки.

Что будет в видео?

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

А еще в видео кот на гитаре играет!

Репозиторий: https://github.com/easyp-tech/easyp
Документация: https://easyp.tech
Чатик сообщества: @easyptech

Поставьте пацанам звездочек, если проект понравился!

Обсуждение: Какие инструменты для ProtoBuf вы используете? Какие есть пролемы? Что можно было бы добавить в качестве новой фичи в easyp?

| Поддержать | YouTube | GitHub | Чат |
2228👍22🔥11🤯3