Your trial period has ended!
For full access to functionality, please pay for a premium subscription
OP
Находки в опенсорсе
https://t.me/opensource_findings
Channel age
Created
Language
Russian
2.33%
ER (week)
12.16%
ERR (week)

Привет! Меня зовут Никита Соболев. Я занимаюсь опенсорс разработкой полный рабочий день. Тут я рассказываю про #python, #c, интересные проекты, коммиты, доклады, и тд. Поддержать: https://boosty.to/sobolevn

Messages Statistics
Reposts and citations
Publication networks
Satellites
Contacts
History
Top categories
Main categories of messages will appear here.
Top mentions
The most frequent mentions of people, organizations and places appear here.
Found 24 results
58
58
3.1 k
Первый чемпионат по опенсорсной настолке про IT: Ship IT!

Как вы можете знать, я сделал опенсорсую настолку про IT: github.com/sobolevn/ship-it-boardgame
Её можно бесплатно распечатать и поиграть. Она так же есть бесплатно в стиме для Table Top Simulator.

Игра от 2 до 5 игроков, от 30 до 45 минут, для игроков 16+

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

PiterPy

Но, у меня есть новости куда круче. На ближайшем PiterPy – мы устроим первый официальный чемпионат по Ship IT!

Формат: 10 минут на разминочную игру. Далее 3 полных круга, победители проходят дальше.
Ведущий: я!
Призы: за выход в финал – настольная игра. За победу – бесплатный билет на любую конференцию jug.ru по вашему выбору. 🏆

Правила: https://github.com/sobolevn/ship-it-boardgame/blob/master/ru/rules.md
Играем в душный режим с фрилансом!
Версия игры: 0.0.23

Обязательно перед игрой прочитайте правила игры, если еще не играли :) Можно предварительно потренироваться в Steam'е с друзьями или нашей группе.
Дата и время: 16 мая, на вечеринке PiterPy + IML. Начало в 19:30.
Место: Зал 4.

✅ Регистрация на игру обязательна: https://docs.google.com/forms/d/e/1FAIpQLSctYdyrcSPKFY79o4A5-1-FZxIUnoIcjHuELFTDlkoRMA4Q_w/viewform

Жду всех настольщиков на настоящую зарубу!

Если еще не купили билет, то вот промокод на 25% скидки на персональный билет: OPENSOURCEFINDINGS
А для студентов и преподавателей ВУЗов там вообще есть отдельные билеты.

Реклама. ООО «Джуг Ру Груп». ИНН 7801341446 erid 2RanymVajKX
04/24/2025, 16:30
t.me/opensource_findings/907
176
116
3.8 k
PEP 750: t-строки в 3.14

В питон добавили еще один способ форматировать строки. Теперь – со специальным АПИ для внешних интеграций.

- PEP: https://peps.python.org/pep-0750
- Реализация: https://github.com/python/cpython/pull/132662

Основная причина: использовать f строки удобно, но нет никакого АПИ для перехвата момента "вставки" или интерполяции значений. Например, при форматировании html или sql – требуется специальным образом делать escape для значений. И раньше код вида f"
{template}
" представлял собой дыру в безопасности и потенциальное место для XSS.

string.templatelib.Template

Новый префикс t не будет создавать объект str, он будет создавать объект класса string.templatelib.Template:


>>> user = 'sobolevn'
>>> template = t"Hi, {user}"
>>> template
Template(strings=('Hi, ', ''), interpolations=(Interpolation('sobolevn', 'user', None, ''),))

>>> from string.templatelib import Template
>>> isinstance(template, Template)
True


Обратите внимание, что при создании template – у нас не произошло форматирование сразу. Мы создали объект, у которого есть свойства strings и interpolations, из которых можно собрать финальную отформатированную строку.

Давайте посмотрим на примере. Допустим, мы хотим формировать URL из наших данных:


>>> domain = 'example.com'
>>> query = 'python string formatting is too complex'
>>> template = t'https://{domain}?q={query}'


И сам код логики форматирования, где мы будем вставлять значения разным способом. Если у нас шаблон query, то мы будем использовать quote_plus для его форматирования. Остальные значения – будем вставлять как есть:


>>> from string.templatelib import Template, Interpolation
>>> from urllib.parse import quote_plus

>>> def format_url(template: Template) -> str:
... parts = []
... for part in template:
... match part:
... case str() as s: # regular string
... parts.append(s)
... case Interpolation(value, expression='query'):
... parts.append(quote_plus(value))
... case Interpolation(value):
... parts.append(value)
... return ''.join(parts)


И вот результат:


>>> format_url(template)
'https://example.com?q=python+string+formatting+is+too+complex'


Только теперь наш Template был отформатирован. Нами. Ручками.
У нас есть полный контроль за процессом форматирования. Вот в чем суть данного ПЕПа.

Фичи одной строкой

- Работает = как обычно в f строках: t'{user=}'
- Есть привычные определители формата: !r, !s, .2f, тд
- t строки можно конкатенировать: t'Hello' + t' , world!' и t'Hello, ' + 'world'
- Поддерживается режим raw строк: rt"Hi \n!"

Как устроено внутри?

Интересные места имплементации:
- Изменения лексера
- Изменения грамматики языка
- Новое CAPI _PyTemplate
- Новые классы Template и Interpolation написанные на C
- Новый байткод BUILD_INTERPOLATION и BUILD_TEMPLATE


>>> import dis
>>> user = 'sobolevn'
>>> dis.dis('t"Hi, {user}"')
0 RESUME 0

1 LOAD_CONST 2 (('Hi, ', ''))
LOAD_NAME 0 (user)
LOAD_CONST 1 ('user')
BUILD_INTERPOLATION 2
BUILD_TUPLE 1
BUILD_TEMPLATE
RETURN_VALUE


Обсуждение: как вам еще один способ форматирования строк?

| Поддержать | YouTube | GitHub | Чат |
04/23/2025, 11:47
t.me/opensource_findings/906
88
160
3.6 k
Perforator — система непрерывного профилирования для разных языков

https://github.com/yandex/perforator

Главные фичи:

> Efficient and high-quality collection of kernel + userspace stacks via eBPF
> Scalable storage for storing profiles and binaries
> Support of unwinding without frame pointers and debug symbols on host
> Convenient query language and UI to inspect CPU usage of applications via flamegraphs
> Support for C++, C, Go, and Rust, with experimental support for Java and Python
> Generation of sPGO profiles for building applications with Profile Guided Optimization (PGO) via AutoFDO

Но самое главное – у Perforator есть режим Continuous Profiling, где на сервак ставится агент, который передает информацию о производительности всех сервисов. На что тратит всего около 1% CPU.

Очень полезный и полный пост с анонсом на хабре.
Важное ограничение: пока работает только на x86_64 Linux, ARM поддержка планируется в ближайшем будущем.

Профилируем код на Python

Нас конечно же больше всего интересует, как данная штука умеет профилировать код на питоне.
Пока что работают только версии после 3.12, потому что нативная поддержка perf появилась именно там: https://docs.python.org/3/howto/perf_profiling.html

Смотрим доку, как профилировать питон: https://perforator.tech/docs/en/tutorials/python-profiling
Сначала собираем при помощи docker в пару строк: https://perforator.tech/docs/en/guides/build#container

Прямо в примере в доке есть код, который будет работать неоптимально. Запустим его:


» python server.py
My pid is 53000
Serving on port 9007


И запускаем профилировщик: sudo perforator record --pid $YOUR_PID --duration 1m --serve ":9006"
На http://localhost:9006 вас будет ждать flamegraph работы скрипта.

Перед тем, как его смотреть, нагрузим наш сервак простейшим скриптом:


import requests
import random

while True:
user_id = random.randint(1, 1000000)
requests.get(f"http://localhost:9007/search_user?user_id={user_id}")


Получится вот такой замечательный flamegraph.

Но! Пример из документации не заканчивается просто созданием графика, пример показывает, как его анализировать. Что очень важно. Далее нам предлагают найти узкое место в коде: https://perforator.tech/docs/en/tutorials/python-profiling#optimizing-the-bottleneck

После оптимизации получится так. Потребление CPU упадет с 96% до 26%

Чего хочется?

- Хочется поддержки macos для локального профилирования
- Хочется попробовать в реальном проде :)
- Хочется понять, насколько такие данные помогут средней компании писать более производительные сервисы

Обсуждение: как у вас в компаниях дела с Continuous Profiling?

Про Perforator я узнал благодаря своему старому и доброму товарищу – Евгению Антонову, он ведет канал про важные навыки для тимлидов и два подкаста: КодаКода – про менеджмент и харды, "Три тимлида заходят в бар" – про разные тимлидские штуки, которые будут безусловно полезны для тех, кто решил развиваться в данную ветку карьеры. Советую!

Хотите рассказать про свой опенсорс проект? Пишите в наш чат :)

| Поддержать | YouTube | GitHub | Чат |
04/17/2025, 12:50
t.me/opensource_findings/905
90
35
3.5 k
Сложности запуска Docker в CI

Когда я писал прошлый пост про работу CI в GitVerse, я получил несколько вопросов относительно: а как работает Docker-in-Docker (DinD) в таком CI? Я спросил ребят, как они планируют реализовать данную фичу в ближайшем будущем. Ответ получился очень интересным.

Со стороны задача "запустить DinD в публичном CI" не выглядит как-то архи-сложно. Однако, на деле как всегда есть нюансы.

Какие вообще есть варианты запуска DinD?

1. Можно взять docker:dind и прокинуть ему docker.sock, а затем получить побег из курятника, и наблюдать, как пользователи получают полный доступ к машине, где гоняются другие сборки других проектов (с секретами, конечно же). Так делать совершенно точно нельзя!

Вот пример, насколько просто сбежать из такого контейнера (в самом простом случае):


# Запускаем контейнер
» docker run --name=first -v /var/run/docker.sock:/var/run/docker.sock -it docker:dind sh

# Внутри docker:
/ # ls -alh /var/run/docker.sock
srwxr-xr-x root /var/run/docker.sock
/ # hostname
700809c044d6 # <- наш текущий хост, контейнер `first`

/ # docker container ls
CONTAINER ID NAMES
e7d7857b965a other
700809c044d6 first

/ # docker exec -it other sh
/ # hostname
e7d7857b965a # <- мы получили доступ к соседнему контейнеру на хосте :(


Тут – просто вопиющий случай, который делает неправильно буквально все: выставляет docker.sock и использует root внутри контейнера. Даже если вам нужно выставить docker.sock, то есть варианты лучше

2. Можно взять docker:dind и запустить его с --privileged, прокинуть ему DOCKER_TLS_CERTDIR, запустить второй контейнер "клиент" без --privileged, но с нужными сертификатами, и выполнять все на нем. Такой способ уже безопаснее, но все равно есть много вариантов побега и privilege escalation

Я подготовил пример такой сборки: https://gitverse.ru/sobolevn/dind-demo

3. Можно запускать контейнеры в изолированной виртуалке, которая будет быстро стартовать, работать и умирать. 0 рисков, никаких общих сокетов и возможности сбежать

GitHub и Packer

GitHub пошел по третьему пути. Когда мы указываем в actions:


jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: wemake-services/wemake-python-styleguide@1.1


То происходит следующее:
- GitHub берет образ виртуалки ubuntu-latest из заранее подготовленных
- Быстро разворачиваем готовый образ при помощи Instant Restore / InPlace Restore из Azure
- GitHub запускает контейнер с wemake-services/wemake-python-styleguide и выполняет код action внутри docker

Но, внутри образов есть не только docker, там есть всё. Образ ubuntu весит 18GB 🫠
Но есть и минимальные виртуалки без всего. Собираются они при помощи packer.

Планы

GitVerse прямо сейчас разрабатывают что-то очень похожее. В планах:
- Разные ОС: разные linux, macos, windows
- Разные архитектуры: x86_64, arm

Кажется, что такой путь – очень удобный. Быстро, надежно, кастомизируемо.

Подпишись на их канал @gitversenews, чтобы быть в курсе всех новостей. Поддержка заинтересованных в развитии опенсорса продуктов и компаний, таких как GitVerse, помогает мне бесплатно делиться контентом с вами. Спасибо им большое за поддержку и помощь в подготовке поста.

Реклама. АО «СберТех» ИНН:7736632467 Erid:2W5zFJeNAVn Сайт: https://gitverse.ru/home
18+
04/14/2025, 16:04
t.me/opensource_findings/904
92
164
11 k
PythoNN: видео с апрельского митапа

4 апреля прошел очередной #python митап в Нижнем Новгороде.

Было очень душевно и интересно.
Случился аншлаг! Пришло много нижегородцев и приехало очень много гостей: из Москвы, Питера, Кирова и других городов. Спасибо всем!

Было 4 крутых доклада:
- "Are you NATS?" – Гурбанов Михаил https://youtube.com/watch?v=atD3JVWurno
- "Почему исправление опечаток сложнее, чем кажется, и как мы с этим српавляемся" – Дмитрий Бровкин https://youtube.com/watch?v=9HRBwwaMIfA
- "Современный web с современными темплейтами" – Алексей Гончарук https://youtube.com/watch?v=lN3Pz_hUCio
- "Демистификация PostgreSQL-индексов" – Алексей Голобурдин https://youtube.com/watch?v=6kVGSLdj28k

А потом мы сидели в баре до 5 утра.

Что улучшить?
- Первый раз записывал на StreamYard, сделал плохую композицию слайдов и видео докладчика, исправим в следующий раз. Прикрепил все слайды в описании докладов – чтобы была возможность все прочитать и скопировать код
- Поработаем над звуком, сейчас он немного прыгал

Хотите присоединиться?
- Если хотите сделать доклад, пишите мне в личку – лично учу новичков выступать и делать слайды, полная свобода в выборе темы
- Если хотите просто послушать – следите за анонсами в чате и подписывайтесь на мой канал с записями

У нас в Нижнем – просто офигенно, всех ждем в гости! 🌆

| Поддержать | YouTube | GitHub | Чат |
04/09/2025, 19:25
t.me/opensource_findings/903
162
37
3.2 k
PEP 649 и PEP 749: `__annotate__`

- https://peps.python.org/pep-0649
- https://peps.python.org/pep-0749

История

Одна из самых больших проблем Python – непродуманность аннотаций. Особенно их работа в рантайме.
В чем основная проблема?


class A:
@staticmethod
def build() -> A: # <- тут будет NameError, потому что в теле класса `A` имя `A` еще не определено
return A()


В python3.10 хотели по-умолчанию включить from __future__ import annotations, что превращает все аннотации в строки на уровне компилятора:


static int
codegen_visit_annexpr(compiler *c, expr_ty annotation)
{
location loc = LOC(annotation);
ADDOP_LOAD_CONST_NEW(c, loc, _PyAST_ExprAsUnicode(annotation));
return SUCCESS;
}


Но, не вышло. Такой подход ломает кучу кода, который резолвит аннотакции из строк в объекты. Пример:


from __future__ import annotations

def some():
class A: ...
def build_a() -> A:
return A()
return build_a

import typing
typing.get_type_hints(some()) # NameError: A


Что будет в 3.14?

Взамен - еще с версии 3.11 - начали писать новый PEP про новый способ резолвить аннотации на основе дескрипторов. Меня даже в ПЕПе упомянули за вклад в данную фичу. Теперь компилятор не будет создавать __annotations__ словарь во время компиляции, как раньше:


/* Every annotated class and module should have __annotations__. */
if (find_ann(stmts)) {
ADDOP(c, loc, SETUP_ANNOTATIONS);
}


Теперь компилятор будет генерировать функцию __annotate__ в теле класса или функции, или модуля:


RETURN_IF_ERROR(codegen_nameop(c, loc, &_Py_ID(__annotate__), Store));


Теперь все аннотации стали ленивыми по-умолчанию.
А уже __annotate__ будет возвращать правильные аннотации для установки в __annotations__, можно переопределить руками и проверить:


>>> class A:
... def __annotate__(format): # TODO: support all formats
... print(f'{format=}')
... return {'a': int}

>>> A.__annotations__
format=1
{'a': }


В качестве format могут быть 3 публичных вида:
- VALUE – дефолт, чтобы возвращать реальные типы
- FORWARDREF – чтобы возвращать annotationlib.ForwardRef для значений, которые "еще не определены", как в примере с классом A в самом начале
- STRING – для возвращения строк, как при __future__.annotations, смотри _Stringifier

Как теперь получать аннотации?

Мы добавили модуль annotationlib, который теперь резолвит аннотации самым правильным способом. Пример:


>>> from typing import TypedDict

>>> class User(TypedDict):
... email: str
... friends: User

>>> from annotationlib import get_annotations, Format

>>> get_annotations(User) # <- VALUE is default
{'email': , 'friends': }

>>> get_annotations(User, format=Format.FORWARDREF) # <- will be able to return VALUE in this case
{'email': , 'friends': }

>>> get_annotations(User, format=Format.STRING)
{'email': 'str', 'friends': 'User'}


А вот реализация __annotate__ для TypedDict. Очень хороший пример для вашего кода.

В следующих постах расскажу про модуль annotationlib, inspect.get_annotations, проблемы и сложности данной фичи. Подписывайся!
Обсуждение: как вам данная фича? Будете использовать в своих проектах?

| Поддержать | YouTube | GitHub | Чат |
04/08/2025, 19:17
t.me/opensource_findings/902
139
58
4.2 k
https://www.youtube.com/watch?v=wgxBHuUOmjA

Добавил ключевое слово maybe в Python3.14

https://github.com/python/cpython/pull/131982 🎉
04/01/2025, 18:54
t.me/opensource_findings/901
https://www.youtube.com/watch?v=wgxBHuUOmjA

Добавил `maybe` keyword в Python3.14

https://github.com/python/cpython/pull/131982 🎉
04/01/2025, 18:53
t.me/opensource_findings/900
200
71
3.7 k
PEP765: больше никакой грязи в finally

Ссылка на PEP: https://peps.python.org/pep-0765
Одна из самых сломанных частей питона была пофикшена в 3.14

В чем проблема?

Ранее такой код вел себя крайне странно:


>>> def some():
... try:
... return 1
... finally:
... return 2

>>> some()
2


Так как из функции может быть только один return, а код в finally всегда выполняется – то получаем, что получаем.

Или такой код:


>>> def other():
... try:
... 1 / 0
... finally:
... return 2

>>> other()
2


Тут вообще жесть. У нас подавляется исключение без except 🫠
Аналогично можно делать не только с return, но и с break / continue в циклах:


>>> def cycle():
... for i in range(2):
... try:
... i / 0
... finally:
... print(i)
... continue
... return 2

>>> cycle()
# prints: 0
# prints: 1
# returns: 2


Такой код позволял создавать ошибки на ровном месте.

Как исправили?

Теперь такой код генерирует SyntaxWarning в 3.14:


>>> def some():
... try:
... return 1
... finally:
... return 2
:5: SyntaxWarning: 'return' in a 'finally' block


Скоро – будет SyntaxError, в будущих версиях.
Но, WPS, например, запрещал такое делать уже очень давно.

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

Сам патч очень маленький и простой:

1. Добавляет список текущего контекста _Py_c_array_t cf_finally; в ast_opt.c

Храним внутри структуры вида:


typedef struct {
bool in_finally; // мы в `finally`?
bool in_funcdef; // мы в `def` или `async def`?
bool in_loop; // мы в `for`, `async for` или `while`?
} ControlFlowInFinallyContext;


2. При обходе дерева, добавляет нужные данные в текущий контекст: находимся ли мы в finally, функции, цикле
3. Если находим return, break или continue, то выполняем проверку синтаксиса; вот код для return:


static int
before_return(_PyASTOptimizeState *state, stmt_ty node_)
{
if (state->cf_finally_used > 0) {
ControlFlowInFinallyContext *ctx = get_cf_finally_top(state);
// если нашли `return` в `finally`, но не во вложенной функции,
// то показываем warning пользователю:
if (ctx->in_finally && ! ctx->in_funcdef) {
if (!control_flow_in_finally_warning("return", node_, state)) {
return 0;
}
}
}
return 1;
}


4. Внутри control_flow_in_finally_warning используем специальное АПИ для SyntaxWarning:


static int
control_flow_in_finally_warning(const char *kw, stmt_ty n, _PyASTOptimizeState *state)
{
PyObject *msg = PyUnicode_FromFormat("'%s' in a 'finally' block", kw);
if (msg == NULL) {
return 0;
}
int ret = _PyErr_EmitSyntaxWarning(msg, state->filename, n->lineno,
n->col_offset + 1, n->end_lineno,
n->end_col_offset + 1);
Py_DECREF(msg);
return ret < 0 ? 0 : 1;
}


Готово!

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

P.S. Пока писал пост, нашел багу в питоне и важную опечатку :)

| Поддержать | YouTube | GitHub | Чат |
03/31/2025, 09:13
t.me/opensource_findings/899
102
71
3.6 k
wemake-python-styleguide@1.1.0

Вышла новая версия самого строго линтера для питона. Теперь еще строже!

Главная фича релиза: wps explain CLI, которая позволяет видеть вывод информации: почему что-то запрещено, и как такое исправить.

А так же несколько новых правил:
- WPS476 не дает использовать await в for (потому что вы скорее всего хотите использовать asyncio.gather, чтобы добиться асинхронности)
- WPS477 запрещает сложную комбинацию TypeVarTuple рядом с TypeVar с дефолтным значением: class Class[T=int, *Ts=*tuple[int, ...]]:

Ну и много разных багов поправили, куда без них.
Полный список изменений: https://github.com/wemake-services/wemake-python-styleguide/releases/tag/1.1.0

Большое спасибо участникам нашего чата за PRы, они затащили релиз 🧡
Обсуждение: каких правил в wemake-python-styleguide вам не хватает? Какие душат вас сильнее всего? Что можно улучшить?

| Поддержать | YouTube | GitHub | Чат |
03/25/2025, 13:40
t.me/opensource_findings/898
139
190
4.2 k
Находки в опенсорсе: taskiq

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

Сегодня в опенсорсе я нашел современную замену Celery с асинхронностью и тайпхинтами.
Поговорили с автором про то, как устроена библиотека внутри:
- Как сделать универсальные интерфейсы для всех видов очередей
- Что Redis – вообще-то не очередь, и не стоит его использовать для таких задач
- Как устроена асинхронность библиотеки внутри
- Как запускать задачи по расписанию
- Как делать сложные canvas для задач с несколькими шагами
- Обсудили модульность: каждая реализация живет в отдельном пакете

Библиотека выглядит солидно! 🌚

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

Если ваши коллеги все еще хотят делать celery таски с asyncio.run внутри – срочно покажите им видос и уберегите от греха!

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

| Поддержать | YouTube | GitHub | Чат |
03/18/2025, 10:41
t.me/opensource_findings/897
107
47
2.9 k
mlut - новое слово в подходе Atomic CSS

mlut (читается как млат) - это инструмент для верстки в подходе Atomic #css, с которым можно создавать стили любой сложности. Что-то похожее на Tailwind, но по некоторым параметрам превосходит все популярные аналоги.

Atomic CSS - это методология верстки, в которой мы используем маленькие атомарные классы, каждый из которых делает одно действие. Эти классы называют утилитами. Обычно они применяет одно CSS-свойство (например, цвет текста), но не обязательно одно. Выглядит в коде это примерно так:






.D-ib {
display: inline-block;
}

.P1r {
padding: 1rem;
}

.Bgc-blue_h:hover {
background-color: blue;
}


Преимущества такого подхода

1. Тратим меньше мыслетоплива: не думаем о нейминге сущностей, структуре каталогов
2. Меньше CSS на клиенте: реиспользуем одни и те же утилиты, а новые стили почти перестают добавляться
3. Быстрее пишем стили: короткие классы, нет переключения файлов
4. Можно применять на любом стеке: JS SPA, Ruby, Clojure, etc

Возвращаясь к mlut, вот чем он отличается от конкурентов

Строгий нейминг

Tailwind:
- flex => display: flex, но flex-auto => flex: 1 1 auto
- tracking-wide => letter-spacing: 0.025em
- justify-* => content, items, self?

mlut:
- Js-c => justify-self: center
- Bdr => border-right: 1px solid
- Bdrd1 => border-radius: 1px

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

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

Почти произвольные значения by design

- значения утилит: Ml-1/7 =>

margin-left: -14.3%

- states: Bgc-red200_h,f =>

.Bgc-red200_h\,f:hover,
.Bgc-red200_h\,f:focus {
/* ... */
}

- at-rules: @:p-c,w>=80r_D-f =>

@media (pointer: coarse), (min-width: 80rem) {
/* ... */
}


Хорошая эргономика

Более короткие className:









Удобный синтаксис для сложных утилит

Tailwindcss:
- [@media(any-hover:hover){&:hover}]:opacity-100
- text-[length:var(--myVar,1.3rem)]
- supports-[margin:1svw]:ml-[1svw]

mlut:
- @:ah_O1_h =>

@media (any-hover) {
.\@\:ah_O1_h:hover {
opacity: 1
}
}

- Fns-$myVar?1.3 =>

font-size: var(--ml-myVar, 1.3rem);

- @s_Ml1svw =>

@supports (margin-left: 1svw) {
.\@s_Ml1svw {
margin-left: 1svw
}
}


Удобно расширять

Утилиты, states и кастомные at-rules добавляются парой строк кода

@use 'mlut' with (
$utils-data: (
'utils': (
'registry': (
'Mil': margin-inline,
),
),
),
);

@include mlut.apply('Mil-13');

// CSS

.Mil-13 {
margin-inline: -13px;
}


Чем еще может похвастаться mlut

- Написан на Sass, все плюсы которого идут в комплекте
- Доступен как JIT (on demand), так и AOT режим работы
- Отличная кастомизация

Состояние проекта

Что реализовано на сегодня:

- Генератор утилит почти любой сложности
- JIT-движок, который умеет генерировать утилиты из HTML/JSX/etc
- CLI с минификацией и автопрефиксером
- Плагины для сборщиков фронтенда: Webpack, Vite и Rollup
- Документация
- Интерактивный мини-курс по инструменту совместно с HTML Academy
- Больше технических деталей есть в расшифровке доклада с HolyJS.

Автор будет благодарен любому фидбеку и особенно звездам на гитхабе!
03/17/2025, 12:46
t.me/opensource_findings/896
Комменты сломались! Можно писать сюда :)
03/12/2025, 15:44
t.me/opensource_findings/895
96
29
3.0 k
Лучший курс по Python 14: Steering Council

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

Пригласил Donghee Na, одного из 5 членов Steering Council – ключевого органа управления разработкой CPython – рассказать о своей работе.

Мне кажется, что разработчикам – важно понимать, как развиваются инструменты, которыми они пользуются. Как принимаются технические решения, как происходит обсуждение. И что в таких решения можно и нужно участвовать!

Затронули крайне важные темы:
- Free-threading
- JIT
- Tail-call dispatch и faster cpython

Donghee оставил свои контакты, если кто-то хочет серьезно начать работу над free-threading.
А я получил большое удовольствие от нашего общения. Надеюсь, что вы тоже оцените.

Советую смотреть интервью с субтитрами: есть на русском 🇷🇺 и на английском 🇺🇸.

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

| Поддержать | YouTube | GitHub | Чат |
03/12/2025, 15:38
t.me/opensource_findings/894
Repost
131
45
3.0 k
Привет! Стартуем новый проект для любителей опенсорса: помогаем меинтейнерам и контрибьюторам найти друг друга.

Как оно работает?
- В данном канале меинтейнеры разных Python проектов (от CPython, mypy, Litestar до taskiq) могут в любой момент выложить простые задачки, чтобы люди могли принять участие в разработке их проекта
- Если вы хотите поработать над задачкой – напишите в самой задаче на гитхабе: "Can I work on this?", получите подтверждение меинтейнера и приступайте
- Делитесь успехами / задавайте вопросы в нашем чате @opensource_findings_chat

Если вы меинтейнер какого-то крупного проекта (>= 100 ⭐), то пишите в чат – вас добавят как админа, чтобы вы смогли постить в канал свои задачи. Чем больше – тем лучше, не забывайте ставить тег своей технологии.

Всем хорошего опенсорса!
03/12/2025, 12:30
t.me/opensource_findings/893
72
23
2.9 k
Как работает диспатчеризация байткода внутри VM? Tail call dispatch

(перед прочтением – советую прочитать пост ^ про computed goto)

https://github.com/python/cpython/pull/128718

В CPython новая оптимизация, которая дает где-то 5% производительности. Я уже рассказывал, что такое computed goto, но теперь есть еще более прикольная и быстрая штука для диспатчеризации байткода.

То есть: вызов следующего опкода в Python коде будет быстрее, а значит – все программы просто бесплатно станут быстрее.

(не путать с tail call оптимизацией для рекурсии)

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

Сначала делаем два макроса, которые будут устанавливать нужные атрибуты для компилятора.
Пока только [[clang::musttail]], про поддержку компиляторов будет ниже. Зачем нужен preserve_none – можно прочитать тут.


#ifdef Py_TAIL_CALL_INTERP
// Note: [[clang::musttail]] works for GCC 15, but not __attribute__((musttail)) at the moment.
# define Py_MUSTTAIL [[clang::musttail]]
# define Py_PRESERVE_NONE_CC __attribute__((preserve_none))

// Для простоты еще два макроса, просто слишком часто повторяется код:
#define TAIL_CALL_PARAMS _PyInterpreterFrame *frame, _PyStackRef *stack_pointer, PyThreadState *tstate, _Py_CODEUNIT *next_instr, int oparg
#define TAIL_CALL_ARGS frame, stack_pointer, tstate, next_instr, oparg


Далее, создаем новый тип колбеков для "tail-call функций":


Py_PRESERVE_NONE_CC typedef PyObject* (*py_tail_call_funcptr)(TAIL_CALL_PARAMS);


Важный шаг: меняем дефиницию макросов TARGET и DISPATCH_GOTO по аналогии с computed gotos.
Теперь тут будет:


# define TARGET(op) Py_PRESERVE_NONE_CC PyObject *_TAIL_CALL_##op(TAIL_CALL_PARAMS)
# define DISPATCH_GOTO() \
do { \
Py_MUSTTAIL return (INSTRUCTION_TABLE[opcode])(TAIL_CALL_ARGS); \
} while (0)


То есть теперь по факту – все TARGET макросы будут разворачиваться в отдельные функции:


Py_PRESERVE_NONE_CC static PyObject *_TAIL_CALL_BINARY_OP(TAIL_CALL_PARAMS);


В теле такой функции будет очень мало кода – только обработка ее логики. Пример для BINARY_OP.
Вот они, для каждого опкода:


Py_PRESERVE_NONE_CC static PyObject *_TAIL_CALL_BINARY_OP(TAIL_CALL_PARAMS);
Py_PRESERVE_NONE_CC static PyObject *_TAIL_CALL_LIST_APPEND(TAIL_CALL_PARAMS);
// ...


И мы так же ищем следующий опкод в INSTRUCTION_TABLE[opcode], но теперь мы вызываем функцию, которая там лежит в DISPATCH_GOTO. То есть теперь – у нас теперь есть буквально:


callbacks = {
'BINARY_OP': lambda *args, **kwargs: ...
'LIST_APPEND': lambda *args, **kwargs: ...
}

callbacks[opcode](*args, **kwargs)


И во время конфигурации сборки питона – проверяем, поддерживает ли наш компилятор такое.

Так почему быстрее?

Теперь – все функции маленькие, их удобно оптимизировать. Вот тут уточнение из комментов.

Потому что для [[mustail]] не создается дополнительный стекфрейм, asm получается более оптимальным. Я подготовил для вас пример: https://godbolt.org/z/T3Eqnd33e (для таких простых случаев -O2 более чем работает, но все равно)

Для вызова функции foo(int a) было:


mov edi, dword ptr [rbp - 4]
call foo(int)@PLT
add rsp, 16
pop rbp
ret


Стало:


mov edi, dword ptr [rbp - 4]
pop rbp
jmp foo(int)@PLT


call -> jmp!

Статья по теме от автора __attribute__((musttail))

Ограничения

Пока что данное поведение скрыто за флагом --with-tail-call-interp, по-умолчанию в 3.14 оно работать не будет. В следующих версиях – включат по-умолчанию для всех.

Есть еще и техническое ограничение. Пока что такой __attribute__ компилятора поддерживает только clang в llvm>=19 на x86-64 и AArch64. В следующем релизе gcc, вроде бы, завезут поддержку

Ну и последнее: пока проверили только перформанс с Profile Guided Optimization (pgo), сколько будет без него – еще не мерили. Сначала вообще заявили прирост на 15%, но потом нашли баг в llvm, который замедлял код без такой фичи.

Да, у нас тут с вами душный канал, где нет ярких заголовков :(

Обсуждение: чего ждете от 3.14 больше всего?

| Поддержать | YouTube | GitHub | Чат |
03/09/2025, 11:28
t.me/opensource_findings/892
101
130
3.4 k
zen browser

После недавней оказии с FireFox, я понял, что нужно менять свой браузер.
Выбор пал на zen (почти arc, но для firefox), потому что я люблю минимализм.

Что мне нужно от браузера?

- Несколько вкладок, у меня их никогда не бывает сильно много, я все их закрываю примерно раз в день
- Панель для ввода адреса с минимумом функциональности (подсказки, история, поиск)
- Минималистичный интерфейс, без лишних кнопок
- Поддержка uBlock, нескольких других похожих плагинов
- Приватность по-умолчанию

Все. Остальные фичи мне скорее мешают. Я не пользуюсь закладками, workspacе'ами, профилями, синками и тд.

Что есть в zen?

Во-первых, браузер почти полностью позволяет убрать свой интерфейс, что приятно. Теперь по пунктам:
- Hidden Tabs: можно настроить "compact mode", чтобы вкладки исчезали, когда они не нужны, нажатие cmd+b показывает вкладки, нажатие cmd+1 открывает первую вкладку и тд
- Floating Nav Bar: После настройки панель навигации сверху исчезает, когда ей не пользуешься (открывается на cmd+t для открытия новой вкладки и cmd+L фокуса в текущей)
- Busy Mode: при нажатие ctrl+b включает интерфейс, если нужно что-то найти, если идет какой-то напряженный рабочий режим
- Tab Preview: отключаемая фича, которая позволяет сделать превью страницы и быстро ее закрыть, выглядит полезно для поиска
- Split View: отключаемая фича, которая позволяет открывать две вкладки слева и справа (у меня на `alt-v`) или сверху и снизу (`alt-h`), выглядит полезно для ревью PRов на гитхабе

Ну и конечно же работают все плагины для FireFox и даже есть свои уникальные.
Сверху я все шлифанул кастомным CSS для уничтожения некоторых объектов UI, которые меня отвлекали.

Пока пробую – и мне нравится.

Обсуждение: что сейчас еще есть интересного и удобного в мире браузеров?

| Поддержать | YouTube | GitHub | Чат |
03/02/2025, 18:19
t.me/opensource_findings/891
93
17
2.6 k
Находки в опенсорсе: mypy@2.0

Так как чуваки на бусти собрали цель в 50 человек, я сделал видео, которое обещал.

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

Теперь "Находки в опенсорсе" еще и в видео формате!
Рассказываю, что будет в новом релизе: что сломаем, что добавим.

Пока релиз планируется где-то на вторую половину года, а я уже про него рассказываю.

Если понравится формат – поддержи видео, покажи коллеге :)

| Поддержать | YouTube | GitHub | Чат |
02/20/2025, 15:21
t.me/opensource_findings/889
75
68
2.2 k
Что такое GIL в Python? Вторая часть

Я не закончил! 🌚

Мы не поговорили про очень важную часть: переключение тредов интерпретатором. Треды могут быть очень долгими и не отпускать GIL, мы должны дать поработать каждому.

Простой пример, что так оно и работает:


import threading

def first():
while True:
print('first')

def two():
while True:
print('two')

a = threading.Thread(target=first).start()
b = threading.Thread(target=two).start()


Выдаст что-то вроде:


first
two
first
first
two
two
two
first
first


Как VM CPython переключает потоки?

Важно: у CPython нет своего scheduler'а для тредов. Он целиком полагается на OS в данном вопросе. Однако, GIL дает возможность переодически останавливать работу с Python API одного потока и дать время какому-то другому. Какому – решает уже OS. Но как?

Во-первых, у нас есть замечательный sys.setswitchinterval и sys.getswitchinterval, которые отвечают за примерное время работы одного потока, которое хранится в _gil_runtime_state:


unsigned long _PyEval_GetSwitchInterval(void)
{
PyInterpreterState *interp = _PyInterpreterState_GET();
struct _gil_runtime_state *gil = interp->ceval.gil;
assert(gil != NULL);
return gil->interval;
}


Примерный вывод:


>>> import sys
>>> sys.getswitchinterval()
0.005


Далее: значение gil->interval будет использовано для вызова pthread_cond_timedwait. Мы ждем interval для передачи события (чтобы нам дали GIL) через gil->cond. Если уходим в таймаут, значит пришло время забирать GIL на следующем выполнении байткода силой. Смотри про сигналы и PyCOND_T тут.


MUTEX_LOCK(gil->mutex);

unsigned long interval = (gil->interval >= 1 ? gil->interval : 1);
int timed_out = 0;
COND_TIMED_WAIT(gil->cond, gil->mutex, interval, timed_out);

/* If we timed out and no switch occurred in the meantime, it is time
to ask the GIL-holding thread to drop it. */
if (timed_out && _Py_atomic_load_int_relaxed(&gil->locked)) {
PyThreadState *holder_tstate =
(PyThreadState*)_Py_atomic_load_ptr_relaxed(&gil->last_holder);
assert(_PyThreadState_CheckConsistency(tstate));

_Py_set_eval_breaker_bit(holder_tstate, _PY_GIL_DROP_REQUEST_BIT);
}

Когда флаг _PY_GIL_DROP_REQUEST_BIT будет установлен, мы сможем в _Py_HandlePending передать GIL кому-то другому:


/* GIL drop request */
if ((breaker & _PY_GIL_DROP_REQUEST_BIT) != 0) {
/* Give another thread a chance */
_PyThreadState_Detach(tstate);

/* Other threads may run now */

_PyThreadState_Attach(tstate);
}


После вызова _PyThreadState_Detach(tstate) текущий тред потеряет GIL. И снова будет ждать его при вызове _PyThreadState_Attach(tstate). Пока другой работает.

Часть про VM

Теперь вопрос, а кто вызывает _Py_HandlePending и когда?

Вызывается оно из специального "псевдо-опкода" _CHECK_PERIODIC. Раньше там был макрос CHECK_EVAL_BREAKER, и его иногда забывали добавить в нужные места. Оттого события ОС не обрабатывались, GIL не переключался, было весело.


op(_CHECK_PERIODIC, (--)) {
_Py_CHECK_EMSCRIPTEN_SIGNALS_PERIODICALLY();
QSBR_QUIESCENT_STATE(tstate);
if (_Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker) & _PY_EVAL_EVENTS_MASK) {
int err = _Py_HandlePending(tstate);
ERROR_IF(err != 0, error);
}
}

// Вызываем `_CHECK_PERIODIC` в конце каждого `CALL`.
macro(CALL) =
_SPECIALIZE_CALL
+ unused/2
+ _MAYBE_EXPAND_METHOD
+ _DO_CALL
+ _CHECK_PERIODIC;


Вот теперь мы знаем, как переключаются треды внутри CPython.

Одной строкой

- Замечательная (и более длинная) статья Андрея Светлова на русском про GIL, которую не вчера скинули в нашем чате (реклама)
- Предложение по отмене PyGILState АПИ, потому что оно не работает нормально с субинтерпретаторами

Если интересно – закидывайте в коллег!

| Поддержать | YouTube | GitHub | Чат |
02/18/2025, 11:45
t.me/opensource_findings/888
138
235
6.5 k
Что такое GIL в Python?

Кажется, один из золотых вопросов для всех питонистов на собеседованиях.
Обычно, на встречный вопрос "а что конкретно в питоне является GIL?" не может ответить ни один спрашивающий.

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

Global Interpreter Lock не позволяет более чем одному треду работать с Python API за раз. Его можно отключить через --disable-gil в 3.13+, но сегодня мы про такое не будем.

Обратите внимание на ключевую фразу "c Python API". С системными треды могут и должны работать в режиме настоящей параллельности, без GIL. Что и позволяет получить ускорение при использовании threading, когда C код поддерживает такой способ.

Знакомьтесь – вот структура GIL _gil_runtime_state и поведение в ceval_gil.c.

Как можно отпустить GIL?

На уровне C есть макросы: Py_BEGIN_ALLOW_THREADS и Py_END_ALLOW_THREADS, которые отпускают GIL в нужных местах. Пример из модуля mmap:


Py_BEGIN_ALLOW_THREADS
m_obj->data = mmap(NULL, map_size, prot, flags, fd, offset);
Py_END_ALLOW_THREADS


Или time.sleep, который тоже дает работать другим тредам, пока ждет.

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


{
PyThreadState *_save;
_save = PyEval_SaveThread();
// your code here
PyEval_RestoreThread(_save);
}


PyThreadState является текущим состоянием треда в CPython. Внутри хранится много контекста. Нас особо сильно интересует часть с полями про GIL:


struct PyThreadState {
struct {
unsigned int initialized:1;
/* Has been bound to an OS thread. */
unsigned int bound:1;
/* Has been unbound from its OS thread. */
unsigned int unbound:1;
/* Has been bound aa current for the GILState API. */
unsigned int bound_gilstate:1;
/* Currently in use (maybe holds the GIL). */
unsigned int active:1;
/* Currently holds the GIL. */
unsigned int holds_gil:1;
} _status;

// Thread state (_Py_THREAD_ATTACHED, _Py_THREAD_DETACHED, _Py_THREAD_SUSPENDED).
int state;

// ...
}


Когда вызывается PyEval_SaveThread и GIL отпускается, то на самом деле мы просто помечаем текущий PyThreadState как:


tstate->_status.active = 0;
tstate->_status.unbound = 1;
tstate->_status.holds_gil = 0;
tstate->state = detached_state;


И вызываем _PyEval_ReleaseLock, который уже правильно изменит _gil_runtime_state.
Как итог – текущий стейт теряет возможность вызывать какие-либо Python АПИ. Даже, например Py_DECREF, и в тредах есть свой refcount, который работает локально, чтобы можно было его вызывать без GIL.

Как треды берут GIL?

Смотрим на thread_run из _threadmodule.c.


_PyThreadState_Bind(tstate);
PyEval_AcquireThread(tstate);
_Py_atomic_add_ssize(&tstate->interp->threads.count, 1);


Там используется PyEval_AcquireThread, который берет GIL в конкретном треде для работы с Python API.
И дальше – отпускаем.

В следующих сериях поговорим про переключение тредов, ParkingLot API, Mutex'ы и прочее.
Обсуждение: сталкивались ли вы на собесах с вопросами про GIL? Стало ли теперь понятнее?

| Поддержать | YouTube | GitHub | Чат |
02/17/2025, 15:49
t.me/opensource_findings/887
113
63
2.7 k
Лучший курс по Python 13: print

https://www.youtube.com/watch?v=9aQ-GVlC0nY

В рамках данного видео я рассказываю про:
- Файловые дескрипторы
- Буферизацию вывода
- Устройство TextIOWrapper, BufferedWrite, FileIO
- Зачем нам _pyio?
- Что такое syscall write
- Что происходит после вызова syscall на запись

Для лучшего закрепления материала я предлагаю вам поучаствовать в переписывании print на ASM. Внутри:


static long
sys_write_call(const char *msg, Py_ssize_t size)
{
// TODO: allow to pass `fd` as `print(file=...)` does.
long ret;
asm volatile (
// TODO: convert this ugly AT&T ASM into beautiful Intel one:
"mov $1, %%rax\n" // sys_write call number
"mov $1, %%rdi\n" // stdout=1 and stderr=2
"mov %1, %%rsi\n" // `msg` address
"mov %2, %%rdx\n" // `msg_len`
"syscall\n"
"mov %%rax, %0\n" // save the result
: "=r"(ret)
: "r"(msg), "r"(size) // inputs
: "rax", "rdi", "rsi", "rdx" // changed registers
);

// TODO: maybe handle special cases like `EINTR`
return ret;
}


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

Данная задача реально поможет разобраться с print в CPython на самом низком уровне. Мне было очень интересно! Надеюсь, и вам будет.

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


print 'Hello, world!'


Тут print - ключевое слово, а 'Hello, world!' – объект класса bytes.
Еще писали так:


print(1, 2)


Где print - все еще ключевое слово, а (1, 2) - tuple.
И вот так:


from __future__ import print_function

print(1, 2)


Тогда компилятор уже начинал использовать print как функцию. Ужас!

Если вам было полезно и интересно, не забывайте поддерживать:
- Поделиться с коллегами
- Закинуть на бусти: https://boosty.to/sobolevn

| Поддержать | YouTube | GitHub | Чат |
02/10/2025, 11:06
t.me/opensource_findings/886
60
6
2.2 k
--strict-bytes в mypy@1.15

Вообще, внутри mypy есть много всякой дичи, которую нельзя выразить системой типов нормально. И потому разные хаки просто приколачивают гвоздями. Например, int и float связывают псевдо-"наследованием", чтобы штуки вроде 1 + 1.0 == 2.0 работали нормально.

Раньше так было и с bytes / bytearray / memoryview. То есть буквально можно было писать:


def func(arg: bytes) -> None:
assert isinstance(arg, bytes)

func(b'') # ok
func(bytearray(b'123')) # type checks, fails in runtime
func(memoryview(b'abc')) # type checks, fails in runtime


Даже со всеми --strict флагами. Были спрятанные --disable-bytearray-promotion и --disable-memoryview-promotion, но кто же про них знал?

PEP688

Почему было так? Потому что до PEP688 у нас не было возможности выразить C'шный тип Buffer, который появился недавно. И его выражали сначала как просто bytes (да, bytes был синонимом readonly-buffer долгое время), а потом стали выражать как:


ReadOnlyBuffer: TypeAlias = bytes
WriteableBuffer: TypeAlias = bytearray | memoryview | array.array[Any] | mmap.mmap | ctypes._CData | pickle.PickleBuffer
ReadableBuffer: TypeAlias = ReadOnlyBuffer | WriteableBuffer


Теперь все можно выразить при помощи collections.abc.Buffer.

Следовательно, нам больше не нужны type-promote для bytes / bytearray / memoryview. И флаг --strict-bytes убирает такое приведение из mypy:


# --strict-bytes
def func(arg: bytes) -> None:
assert isinstance(arg, bytes)

func(b'') # ok
func(bytearray(b'123')) # Argument 1 to "func" has incompatible type "bytearray"; expected "bytes"
func(memoryview(b'abc')) # Argument 1 to "func" has incompatible type "memoryview[int]"; expected "bytes"


Советую начинать использовать как можно раньше, потому что такое поведение будет включено в --strict с mypy@2.0, так же как и --local-partial-types. Лучше подготовиться заранее.

Два поста за два дня!

Обсуждение: как вы аннотируете объекты, которые принимают Buffer у себя в коде?

| Поддержать | YouTube | GitHub | Чат |
02/07/2025, 12:29
t.me/opensource_findings/885
126
33
2.7 k
Ковыряем внутрянку nogil

Некоторое время назад я прислал безобидный PR, который исправлял поведение list.insert в nogil сборках CPython. Изменений на 3 строчки. И в ревью случилось два интересных момента.

Во-первых, я случайно удалил оптимизацию.

Было:


PyObject **items;
items = self->ob_item;
items[i+1] = items[i];
items[where] = Py_NewRef(v);



0x0000000000132860 <+96>: sub rdx,0x1
0x0000000000132864 <+100>: sub rax,0x8
0x0000000000132868 <+104>: mov rcx,QWORD PTR [rax]
0x000000000013286b <+107>: mov QWORD PTR [rax+0x8],rcx
0x000000000013286f <+111>: cmp rsi,rdx
0x0000000000132872 <+114>: jle 0x132860


Стало:


self->ob_item[i+1] = self->ob_item[i];
self->ob_item[where] = Py_NewRef(v);



0x0000000000132858 <+88>: mov rdx,QWORD PTR [r12+0x28]
0x000000000013285d <+93>: lea rcx,[rax*8+0x0]
0x0000000000132865 <+101>: mov rdi,QWORD PTR [rdx+rax*8]
0x0000000000132869 <+105>: sub rax,0x1
0x000000000013286d <+109>: mov QWORD PTR [rdx+rcx*1+0x8],rdi
0x0000000000132872 <+114>: cmp rsi,rax
0x0000000000132875 <+117>: jle 0x132858


В питоне есть аналогичная оптимизация. Использовать


items = self.items
for _ in whatever:
some_func(items)


более оптимально, чем


for _ in whatever:
some_func(self.items)


если у вас много объектов в whatever.


LOAD_NAME 3 (self)
LOAD_ATTR 8 (items) # <- won't happen when `items = self.items`
CALL 1


Во-вторых, когда мне указали на ошибку, я понял, что я так до конца и не понял разницу между FT_ATOMIC_STORE_PTR_RELAXED и FT_ATOMIC_STORE_PTR_RELEASE.

А вот тут потребуется пояснительная бригада. Смотрите, при использовании nogil, у нас теперь несколько потоков, который выполняют сишный код (который генерирует нужный ASM). И там есть своя специфика. Начнем с того, что есть специальное понятие – Memory Ordering. Базово – как и в каком порядке будут идти обращения от CPU к памяти. Что становится критически важно, когда у нас появляется multi-threading.

Базово, у нас может быть несколько видов memory ordering:
- memory_order_relaxed – Relaxed operation: there are no synchronization or ordering constraints imposed on other reads or writes, only this operation's atomicity is guaranteed
- memory_order_release – A store operation with this memory order performs the release operation: no reads or writes in the current thread can be reordered after this store. All writes in the current thread are visible in other threads that acquire the same atomic variable and writes that carry a dependency into the atomic variable become visible in other threads that consume the same atomic

Полный референс.

__atomic_store_n(&x->ob_item[0], &first, __ATOMIC_RELEASE) будет скомпилировано в


lea rdx, [rsp+12]
mov QWORD PTR [rax], rdx


__atomic_store_n(&x->ob_item[0], &first, __ATOMIC_RELAXED) скомпилируется в


mov rax, QWORD PTR [rbx]
mov QWORD PTR [rax], rdx


Как можно увидеть: в первом случае происходит непосредственное вычисление адреса rsp+12 инструкцией lea. А во втором случае – мы просто работаем со значением в регистре rbx.

Полный пример на godbolt. Тема для меня новая, продолжаю изучать.

Обсуждение: а вы думали, что nogil – оно просто?

| Поддержать | YouTube | GitHub | Чат |
02/06/2025, 15:21
t.me/opensource_findings/884
185
67
2.9 k
Enum и сложность

Enum – один из самых сложных модулей в питоне, я не шучу. Количество нюансов – просто огромное. Так как я последние несколько дней занимаюсь улучшением поддержки Enum в mypy, то я решил рассказать про интересные штуки из модуля enum, которые вы скорее всего могли пропустить.

Доки: https://docs.python.org/3/library/enum.html Их все равно никто не читает.

global_enum

Чудовищная фича. Просто ужас. Засовывает все значения Enum в sys.modules[module].__dict__:


>>> from enum import Enum, global_enum

>>> @global_enum
... class Pets(Enum):
... CAT = 1
... DOG = 2

>>> print(CAT)
CAT


Да, создает новые глобальные константы. Нет, mypy такое пока не поддерживает.
Для чего нужно? Синтаксический сахар для обратной совместимости. Когда были раньше CAT и DOG как константы в модуле, а потом появляется Enum. Но все равно не советую.

_simple_enum


>>> from enum import IntEnum, _simple_enum
>>>
>>> @_simple_enum(IntEnum)
... class Pets:
... CAT = 1
... DOG = 2
...
>>> print(Pets.CAT)
1


Специальный внутренний хелпер для более быстрого создания Enum классов. Используется в основном внутри CPython для ускорения импорта библиотек. Не поддерживается mypy.

@unique

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


@unique # <- will find the problem
class Pets(Enum):
CAT = 1
DOG = 1 # should be 2


и искать её всю жизнь.

Flag и FlagBoundary

Зачем нужны Flag?


from enum import IntFlag, FlagBoundary

class Permission(IntFlag, boundary=FlagBoundary.STRICT):
READ = 0
WRITE = 1


Для сочетания друг с другом: Permission.WRITE | Permission.READ. Есть 4 разных поведения для таких случаев: https://docs.python.org/3/library/enum.html#enum.FlagBoundary

Советую всегда использовать FlagBoundary.STRICT, если сомневаетесь.

Черная дыра для багов и изменений от версии к версии.

member и nonmember

В Enum есть довольно сложная логика, какие объекты считать за member (часть enum), какие за nonmember (просто какие-то объекты).
Кратко:
- Имя не приватное, имя не __dunder__ и имя не _sunder_
- Имя не в _ignore_
- Не вложенный класс и не метод
- Не инстанс nonmember

Пример:


from enum import Enum, member, nonmember

class Example(Enum):
_ignore_ = ['a'] # nonmember
a = 1 # nonmember
b = 2 # member
__c__ = 3 # nonmember
_d_ = 4 # nonmember
e = nonmember(5) # nonmember
__f = 6 # nonmember

def g(self): ... # nonmember

@member
def h(self): ... # member


Думаю, что всем спалось спокойнее без такого знания. Старайтесь делать такие енамы, чтобы не приходилось использовать темную магию. И старайтесь не пользоваться member и nonmember.

Enum в .pyi файлах

Недавно Typing Spec для енамов был изменен. https://typing.readthedocs.io/en/latest/spec/enums.html

Раньше в .pyi файлах мы аннотировали енамы так:


# mymodule.pyi
class Pets(Enum):
CAT: int
DOG: int


Однако, теперь такой способ будет создавать два nonmember'а. Правильный способ:


# mymodule.pyi
class Pets(Enum):
CAT = 1
DOG = 2


Почему? Потому что значения полей – крайне важно для типа Enum.
Для старого кода mypy выкидывает ошибку.

Обсуждение: а вам нравились Enum в Python? А сейчас?

| Поддержать | YouTube | GitHub | Чат |
01/29/2025, 13:45
t.me/opensource_findings/883
Search results are limited to 100 messages.
Some features are available to premium users only.
You need to buy subscription to use them.
Filter
Message type
Similar message chronology:
Newest first
Similar messages not found
Messages
Find similar avatars
Channels 0
High
Title
Subscribers
No results match your search criteria