11222
Привет! Меня зовут Никита Соболев. Я занимаюсь опенсорс разработкой полный рабочий день. Тут я рассказываю про #python, #c, опенсорс и тд. Поддержать: https://boosty.to/sobolevn РКН: https://vk.cc/cOzn36 Связь: @sobolev_nikita
Еще анонсы докладов на бесплатную конференцию 17 октября в НН
Регистрация: https://itgorky.ru/bolshaya-vstrecha
Нам дали впихнуть невпихуемое и дали еще два слота под доклады.
К 6 прошлым докладам. Теперь их 8 на нашей секции.
Наша концепция конференции: ваши любимые опенсорсеры и ютюберы. Ровно их я и пригласил!
- Алексей Голобурдин @t0digital, с канала t0digital/">Диджитализируй: Будущее локальных LLM
Почему для многих задач полностью локальные агенты это будущее, как Microsoft, Apple и hardware-производители идут в сторону локального инференса, и как это использовать разработчику.
- Александр Целуйко, core-разработчик msgspec: Я твой fastapi msgspec'ом ускорял
Почему msgspec быстрее и меньше базовых структур (namedtuple, dataclass и тп) python и orjson (не всегда и везде есстессна).
Что нужно, чтобы fastapi работал на msgspec? Как разогнать Ваш сервис на fastapi ещё быстрее?
Устроим батл про msgspec vs adaptix!
Другие треки
На конференции будут и другие треки!
- Frontend
- DevOps
- InfoSec
- DevRel
- ODS от наших коллег из Нижегородского филиала @nnodsbreakfast
- QA
На них я особо отмечу доклады от моих коллег / друзей:
- DevRel: Ксения Романова @devrel_sklad: Как изменения в разработке уже меняют DevRel
- DevRel: Анастасия Маслова @za_predelami_koda: Не всё, что можно автоматизировать, стоит автоматизировать
- DevRel: Обед с Валентином Домбровским, где он расскажет про строительство сообществ, про мотивацию, про @moscow_python и про MoscowPython Pro
- Frontend: Никита Пастухов @fastnewsdev: Как научить AI красить кнопки
(ха! смотрите, Никита - фронтендер! 🌚)
Тусовка
Уникальная возможность всем вместе погулять, пообщаться, посмотреть город, хорошо провести время.
Будем тусить до и после конфереции, если хотите с нами, то вот специальный одноразовый чат под событие.
Всех приглашаем!
Техническое
- Письмо от нас ждать не надо, мы его высылаем всем. Просто не всем доходит 🌚
- Если зарегались, то смело приходите / приезжайте! Ничего ждать не надо.
Регистрация: https://itgorky.ru/bolshaya-vstrecha
Нижний Новгород, 17 октября
Обсуждение: А какой доклад больше всего ждете вы? С кем больше всего хочется вжарить пивчанского?
PEPы в Python окончательно вышли из-под контроля
Давайте посмотрим, что происходит с ПЕПами в CPython. Там много новых спорных штук.
Начнем с более базовых, перейдем к дичи в конце. Какие-то ПЕПы приняты, но большинство в обсуждении.
Докстринги для TypeAliasType
PEP-846: https://github.com/python/peps/pull/5116
Базовая база: пишем докстринги после ключевого слова type - у нас появляется __doc__ атрибут.
type Timeout = float | None
"""Maximum wait in seconds. """
def get_book_titles_for_author(author: str):
for book in BOOKS:
match book:
case {"title": title, "author": .author}:
yield title
.author, что показывает матч со значением внутри author, а не переназначение переменной author. Если бы добавили $ для "констант", то было $author 🌚
async def agenerator():
yield 1
yield 2
return 3
async def main():
result = yield from agenerator()
assert result == 3
# spam.py
@public
class Public:
...
@private
class Private:
...
# ex.py
import spam
spam.__all__ # ['Public']
__all__ через декоратор. А можно не надо? __all__ используется для from spam import * и не для чего больше. __all__ надо закапывать, а не развивать. Более того, декоратор нельзя повесить на имена и тайп-алиасы. Кажется очень непродуманным решением.export для ... я не знаю чего.__all__. Чем не устраивает простая текущая система? Если имя начинается с _ - то оно protected. Ключевое слово для управления списком! Я против.@ типовым и рантайм оператором для создания Annotated объектов.int @ Field(max=100) должно значить Annotated[int, Field(max=100)]. Нет задачи сократить количество символов, которые мы пишем (а некоторые уже и не пишут). dmr@0.15.0 в рамках #opensource_september, все большое спасибо за участие. Наш самый большой релиз. Пока что!
JIT могут удалить из CPython!
PEP-836: https://peps.python.org/pep-0836
JIT в CPython имеет очень много проблем, давайте их обсудим.
Если вы все пропустили, то вот вам краткий таймлайн всей истории:
- В мае 2021 года создается команда Faster CPython, под руководством Гвидо в MS, которая должна работать над улучшением скорости CPython
- Где-то в 2022м году к команде присоединяется Brandt Butcher, автор первой версии JIT
- В октябре 2023 года, Brandt презентовал свою идею добавления JIT в CPython
- В январе 2024 года, PR был влит в 3.13, создана первая версия Copy-and-Patch JIT
- В апреле 2024 года релиз менеджеры и SC с таких новостей немного прифигели и попросили сделать PEP хотя бы пост-фактум: https://peps.python.org/pep-0744
- В октябре 2024 года выходит 3.13 с JITом, который можно включить опционально
- В мае 2025 года MS увольняет всех участников команды Faster CPython
- В октябре 2025 года выходит 3.14 с JIT бинарниками. Теперь можно было выбрать образ, который планируешь использовать. Free-Threading все еще не поддерживается
- В конце 2025 года добавлен Tracing JIT
- В июне 2026 года SC выпускает объявление, что работа над JIT полностью заморожена до момента пока: не будет опубликован четкий план развития JIT, конкретные метрики успешности проекта, дизайн АПИ (планировали подключать разные JITы в будущем, но от такой идеи отказались)
- В июле 2026 появляется PEP-836 с ответами на вопросы SC, пока его статус "ждет ответа от SC"
Что произойдет, если SC откажется от текущего PEP? Весь код JIT должен быть удален из 3.16+
Возможно ли такое? Вероятность мала, но она пока есть.
А что по скорости?
Перформанс - основная проблема текущего JITа. А точнее отсутствие перформанса.
Официальные цифры такие:
- В 3.13 разница была +-0%, потому что задача была по сути просто добавить инфру для дальнейших оптимизацией. Принимается.
- В 3.14 разброс был от -10% до +20% на разных кейсах. Говорить, что стало быстрее в общем - нельзя, потому надо замерять каждый кусок кода отдельно.
- В 3.15 наконец-то появилась заявленная цель в 5% среднего ускорения. Чего вроде бы добились на всех платформах. Где-то чуть лучше, где-то похуже. Но и кейсы ускорились не все.
- В 3.16 сейчас ставят цель в 10%, пока рано говорить об успешности
Все точные данные можно посмотреть на официальном сайте: https://www.doesjitgobrrr.com
Какие еще большие задачи ждут JIT?
- Необходимо добавить полную поддержку Free-Threading
- Увеличить покрытие тестами
- Добавить поддержку дебаггеров и профайлеров для JIT кода
Цели выше ставились для 3.15, их успешно продолбали. В том числе из-за необходимости писать PEP.
- Добавить 2х человек в команду
- Поддержать LLVM 22 и возможно другие версии
- Полноценная документация
- Возможно получится убрать build-time зависимость от LLVM для сборки JIT, как планировалось ранее
В работе прямо сейчас в рамках 3.16 релиза.
Итоги
Денег больше нет, команда работает на волонтерских началах, сроки жмут, начальство душит. Знакомо? 🌚
Но вера все еще живет!
Я приглашаю вас всех присоединяться к развитию JIT! Давайте вместе сделаем CPython быстрее.
Как? Открывайте задачки, читайте код, реализуйте оптимизации, улучшайте существующие, добавляйте thread-safety для FT, гоняйте бенчмарки, пишите тесты! Работы - буквально вагон.
Обсуждение: Верите ли вы в JIT в CPython? Готовы ли вы помочь?
Одной строкой
- В среду будет митап MoscowPython, где я буду выступать, приходите. Места для регистраций еще есть!
- Релизнули подкаст с @pylounge, поговорили про DMR, псиопы в разработке, общее ощущение усталости и упадка в мировом IT
| Поддержать | sobolevn">YouTube | GitHub | Чат |
Какие уникальные фичи есть в django-modern-rest?
Иногда, когда я добавляю какие-то фичи в мой https://github.com/wemake-services/django-modern-rest ⭐, то я думаю про себя: почему таких фичей больше нет нигде?
Давайте сегодня посмотрим на них. А вы мне расскажите свое мнение в комментах.
Семантическая схема
Допустим, вы навесили на какой-то свой endpoint auth:
class UserController(Controller[MsgspecSerializer]):
@modify(auth=[JWTAsyncAuth()])
async def get(self) -> User: ...
auth (401).throttling=[SyncThrottle(1, Rate.minute)]? Теперь у тебя в ответах автоматом 429. Если нужно, можно отключить любые семантические статусы. error_model как параметр. Можно заменять любые ошибки, все автоматом сконвертится и покажет правильную схему. Зачем? Хочешь Problem Details - используешь. Хочешь свой формат - реализуешь. Можно даже content negotiation на ошибки навесить.
class RequestPayload(pydantic.BaseModel):
username: str
password: str
class ResponsePayload(pydantic.BaseModel):
access: str
refresh: str
class ObtainAccessAndRefreshSyncController(
ObtainTokensSyncController[
PydanticSerializer,
RequestPayload,
ResponsePayload,
],
): ... # надо еще переопределить 2 метода
dj-rest-auth из DRF. Так быть не должно.django-ninja, хоть ванильные вьюхи. И отображать любой внешний OpenAPI. Вот настолько просто:
raw_schema = read_openapi_yaml('openapi.yml')
router = Router(
urls=[
external_path(
'number/', number, name='number',
openapi=load_schema(raw_schema['paths']['/api/number'], PathItem),
),
],
)
django-stubs@6.1 с поддержкой django@6.1
Итог стрима
Что получилось?
- Провести 3 часа в хорошей компании ✅
- Еще до стрима Claude нашел два бага в тестах: один серьезный, один более мелкий (пока мы тестировали сетап для трансляции)
- На стриме был сделан 1 PR с фиксом серьезного бага: https://github.com/wemake-services/django-modern-rest/pull/1196 PR смерджен!
- Сам фикс сначала был полностью навайбкожен, он вроде бы и правил баг, но с точки зрения меинтейнера - все равно было не совсем корректно. Мы не могли показывать такой пример пользователю (баг был в doctest)
- После моих замечаний Claude за 3 итерации ревью (31 коммент за все итерации) смог сделать небольшую правку в проект, задачка примерно джуновского уровня и чистый крудошлеп
- Мы провели неплохое исследование большой задачи с новыми security фичами. Поняли, каких фичей в DMR сейчас не хватает, чтобы сделать хорошо
- В результате исследований Claude сделал второй PR: https://github.com/wemake-services/django-modern-rest/pull/1198
- wemake-python-styleguide, тесты и другие линтеры очень хорошо себя показали: агент был вынужден писать код очень высокого качества
Что не получилось?
- Руками я бы сделал первый PR значительно быстрее
- Во втором PR - нейрослоп
- Мне будет проще его закрыть и сделать самому с нуля, в процессе обдумывая дизайн и АПИ
- К большой фиче из таска мы так и не подошли
Модель: Opus 5
Режим: High Reasoning
Harness: Cursor + набор кастомных скиллов
Токенов потрачено: ~1М
Стоимость в теории: от 5$ до 30$ (но у Никиты подписка, так что реальная стоимость 0$)
Что я думаю?
- ИИ - реально полезный инструмент для некоторых задач, если ты понимаешь, что ты делаешь (или у тебя есть в проекте такой человек)
- У нас появилась возможность править баги в проектах, даже не до конца понимая все детали проекта (пожалуйста, отнеситесь к новой большой силе с большой ответственностью, обратите ее во благо)
- Но то, что пишут в твиттере про "да ИИ теперь ваще все может, программисты не нужны" - маркетинговое преувеличение
- Никита - огромный красавчик, без контекста проекта смог приструнить агентов во время публичного стрима сделать что-то большое и полезное - очень крутой навык
- Про Никитин стек скилов / сетап Claude можно почитать вот тут: /channel/fastnewsdev/361
- Его тема в Cursor'е: https://github.com/pustota-theme/pustota (очень красивая, мне очень понравилась, буду пользоваться!)
Посмотрите запись, кто пропустил: https://www.youtube.com/watch?v=laJQcNAqxc0
Составьте свое мнение!
Обсуждение: Что вы думаете про ИИ хайп? Вам интересно про такое читать? Или уже утомило везде про ИИ слушать? Как вам формат стрима? Повторять? Что можно улучшить?
| Поддержать | sobolevn">YouTube | GitHub | Чат |
id() в питоне не выдает уникальные значения
Наверняка, многие из вас, когда изучали питон читали, что id(obj) дает уникальный идентификатор объекта. Его даже назвали, блин, id 🌚️️
Но, все не совсем так. Сегодня будем срывать покровы с одной из самых простых и сложных механик в питоне.
Что вообще такое id?
Сначала, давайте посмотрим на то, как id() устроен:
static PyObject *
builtin_id_impl(PyModuleDef *self, PyObject *v)
{
PyObject *id = PyLong_FromVoidPtr(v);
if (id && PySys_Audit("builtins.id", "O", id) < 0) {
Py_DECREF(id);
return NULL;
}
return id;
}
idv, id которого хотим узнатьNULL вместе с исключением)int?
int x = 0;
int *y = &x;
x - значение в ячейке памяти. *y - указатель на ячейку x. *y хранит в себе числовой адрес оригинальной ячейки.lea инструкция из x86_64.
PyObject *
PyLong_FromVoidPtr(void *p)
{
// тут еще есть код унификации разных размерностей для разных платформ, но я его выкинул для простоты
return PyLong_FromUnsignedLongLong((unsigned long long)(uintptr_t)p);
}
uintptr_t - числовой тип, в который гарантировано поместится любой адресunsigned long long - достаточно большой числовой тип, который поддерживает C-APIint из указателя. Потому что указатель и есть числовое значение изначально.
x, y, z = 1, 2, 3
t1 = (x, y, z)
id1 = id(t1)
del t1
a, b, c = 4, 5, 6
t2 = (a, b, c)
assert id(t2) == id1
id, доводим его счетчик ссылок до 0, он удаляется, создаем новый такого же размера. Другой объект, другие значения. Получаем такой же id. Пу-пу-пу.freelist оптимизация: мы не выкидываем участки памяти под контейнеры частых размеров, а просто переиспользуем ту же память под новые контейнеры. Потому что аллокация памяти - очень дорогая. Одинаковый участок памяти = одинаковый адрес указателя = одинаковый id.
Подкаст про управление разработкой в новой реальности
ВК | ТГ
Меня пригласили поучаствовать в подкасте, чтобы обсудить разработку с ИИ и управление разработкой с ИИ.
Участники:
- Кирилл Меньшов, старший вице-президент и руководитель блока «Технологии» Сбера
- Дмитрий Иванов, руководитель SourceCraft в Яндексе
- Я, опенсорс разработчик
Поговорили про важные вопросы:
- Сможем ли программировать на русском? Или его тоже потребуется формально верифицировать?
- Как обеспечить должный уровень ИБ, когда софт пишут агенты?
- Как оставить себе "план б", если код все-таки придется смотреть?
- Как расти в новом мире как инженер?
- Откуда брать новых senior разработчиков? И нужны ли они?
- Как починить найм? И почему резюме скорее всего скоро будет не особо нужно
Как вы знаете, я занимаю довольно нейтрально-реалистичную позицию в ИИ вопросах: есть такие-то плюсы и минусы, такие-то риски, такие-то косты, такие-то процессы. В подкасте старался как раз доносить её :)
Полезные ссылки из выпуска:
- Концепция AI-Disrupt PDLC: https://aipdlc.ru/ru
- Язык формальной верификации: https://lean-lang.org
- Tokenmaxxing: https://tokenmaxxing.com
- Прибыльны ли ИИ компании? https://isaiprofitable.com
- Наши скиллы для агентов в DMR
Обсуждение: Что вы думаете? Какие у вас есть страхи / предвкушения от нового ИИ мира разработки? Готовы ли вы программировать на русском языке? Потребуется ли нам смотреть в код?
Буду рад услышать все самые полярные мнения!
erid: 2VfnxvSxct3 Реклама. ПАО "СБЕРБАНК", ИНН 7707083893, 18+
Пользуясь случаем: у нас 10 июля митап в Нижнем Новгороде.
https://pytho-nn.timepad.ru/event/4050146/
В программе 4 крутейших юбилейных доклада от гостей города и (даже!) нижегородца:
- Артем Пашков, Нижний Новгород, Сообщество "Опенсорсеры", Как сообщество Опенсорсеры помогает open source проектам и разработчикам?
Отвечу на главный вопрос: нужно ли лично вам занимать опенсорсом? И как начать?
Расскажу про проблемы, знакомые многим: недостаток внимания к своему проекту, где искать контрибьюторов, куда самому законтрибьютить, а также как получить полезный фидбек.
- Илья Солин, Уфа, ТБанк, /channel/http_418_i_am_a_teapot, Алгебраические эффекты: понять, полюбить и никогда не тащить в прод
Разберём концепцию теоретически (почему это классно), попробуем реализовать её на Python и поймём, стоило ли оно того.
- Георгий Бородин, Москва, Как я перестал бояться и полюбил бойлерплейт
Благие намерения далеко не всегда приводят к хорошему, но если не опускать лапки – наверняка получится сделать систему мечты. Цена этого – вопрос, о котором и хочется рассказать (собираюсь рассказать об одном очень грязном способе якобы оптимизации деливери, который заставил меня ползать по флеймграфам и проклинать себя же и об одном, который позволил мне перестать ждать фронтовых задач).
- Евгений Блинов, The Mutating Company, Из чего состоит фреймворк мутационного тестирования?
В процессе создания своего фреймворка МТ мне потребовалось создать некоторое количество промежуточных библиотек. Подробнее о них расскажу в докладе.
Спикеров можно и нужно мучать вопросами.
Ну а после: афте-пати в баре до закрытия, афте-афте-пати до самого утра.
Ждем всех 10 июля по адресу Алексеевская, 6/16, ИТ Лекторий «Горький Тех»
Сбор гостей с 18:00, стартуем в 18:30
Приходите, приезжайте :)
Генерируем Rust код из Python и становимся крабами
Проект "острый краб": https://github.com/kushaldas/spicycrab
Вы же знаете, что вы узнаете про все важные штуки в питоне первыми? На ближайшем Language Summit в июле Кушал Дас - core-разработчик CPython - представит свой новый проект. Но зачем ждать июля, когда код открыт? Давайте смотреть и пробовать!
В чем главная идея?
- Пишем на типизированном Python
- Получаем на выходе Rust код, который работает в десятки или сотни раз быстрее
- Можем использовать в Python крейты Rust и Python пакеты, что? 🙀
(проект еще не просто в альфе, а в пре-альфе, но мы тут просто любим странное, ставь 🕊, если просто заходишь сюда почитать про непонятное и удивительное)
Начнем с простого: print('Hello world')
Запустим: crabpy transpile ex.py и получим:
fn main() {
println!("Hello world");
}
cookcrab generate clap -o rust-stubs/pip install -e ./rust-stubs/clap_builder ./rust-stubs/clap
from spicycrab_clap import Command, Arg, ArgMatches
def main() -> None:
matches: ArgMatches = (
Command.new("myapp")
.arg(Arg.new("name").required(True))
.get_matches()
)
name: str = matches.get_one("name").unwrap().clone()
print(f"Hello, {name}!")
crabpy transpile ex.py
pub fn main() {
let matches: clap_builder::ArgMatches = clap::Command::new("myapp")
.arg(clap::Arg::new("name").required(true))
.get_matches();
let name: String = matches.get_one::<String>("name").cloned().unwrap().to_string();
println!("{}", format!("Hello, {}!", name));
}
» cargo run -- Nikita
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.01s
Running `/Users/sobolev/Desktop/spicycrab/rusty/target/debug/ex Nikita`
Hello, Nikita!
ruff и uv ровно так и написаны), то есть уже готовые проекты:
use rustpython::vm::pymodule;
#[pymodule]
mod test_module {
#[pyfunction]
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
}
Зеркало PyPI
На нескольких проектах последние несколько дней сталкиваемся с проблемами с доступом к PyPI: как локально, так и в CI. Печально.
All attempts to connect to pypi.org failed.
Probable Causes:
- the server is not responding to requests at the moment
- the hostname cannot be resolved by your DNS
- your network is not connected to the internet
# Установка одного пакета:
pip install attrs --extra-index-url https://pypi-mirror.gitverse.ru/simple/
# Настройка для всех команд:
pip config --user set global.index-url https://pypi-mirror.gitverse.ru/simple/
pip config --user set global.trusted-host pypi-mirror.gitverse.ru
# pyproject.toml
[[tool.poetry.source]]
name = "pypi"
priority = "primary"
[[tool.poetry.source]]
name = "gitverse"
url = "https://pypi-mirror.gitverse.ru/simple/"
priority = "supplemental"
# pyproject.toml
[[tool.uv.index]]
url = "https://pypi.org/simple/"
name = "pypi"
default = true
[[tool.uv.index]]
url = "https://pypi-mirror.gitverse.ru/simple/"
name = "gitverse"
» uv sync --default-index https://pypi-mirror.gitverse.ru/simple/
Resolved 171 packages in 20ms
Checked 102 packages in 13ms
Начали! https://www.youtube.com/watch?v=W9Hd5dfxjIU
Читать полностью…
PEP 810: Explicit lazy imports 2
Как я уже писал, в Python 3.15 нас ждут lazy imports.
В первом посте я описал основные фичи. Прочитайте его перед продолжением.
Во втором посте настало время посмотреть на плохие части.
Детали PEP, которые мы не осветили прошлый раз
Во-первых, из очевидного: lazy import может быть использован только на уровне модуля, в других местах - он будет вызывать ошибку синтаксиса. Но, что забавно, lazy import не может быть использован внутри даже try блоков.
Во-вторых, я не уточнил, как будет работать __lazy_modules__, а там дичь.
Мы можем указывать __lazy_modules__ = ['os', 'typing'] в любой версии питона. Очевидно, что работать как ленивые они будут только в 3.15+, в остальных - будет просто неиспользуемый атрибут. Но штука в том, что в разных версиях питона библиотеки будут работать по-разному. Но! Он будет ленивым, только если он может быть ленивым. То есть, если он находится внутри класса, функции, try, тд - он не станет ленивым. Удачи в дебаге, короче.
Ну и самое забавное, мы можем управлять глобальным стейтом всех импортов через -X lazy_imports=none|normal|all и так же через переменную окружения PYTHON_LAZY_IMPORTS. Что оно значит? Отключаем все lazy импорты | все работает так, как написано | все импорты ленивые. Мы можем управлять тем, как работают импорты через переменную окружения!! Перечитайте, если вы тоже не поняли. Я вот не сразу понял.
Внедрение в питон
В stdlib питона уже активно используют lazy импорты. Однако, внутри уже появились циклические импорты. Потому что теперь так можно сделать случайно. И специально. Да, ленивые импорты могут помогать избегать циклических импортов. В некоторых режимах работы.
Теперь stdlib больше не работает в режиме -X lazy_imports=none.
О чем развернулась жаркая дискуссия, прочитать которую я всем советую: https://github.com/python/cpython/issues/149321
Но и режим -X lazy_imports=all все сломал. Теперь с ним некоторые библиотеки начали работать по-другому. Например:
$ PYTHON_LAZY_IMPORTS=normal ./python -c "import shutil; print(shutil._BZ2_SUPPORTED)"
False
$ PYTHON_LAZY_IMPORTS=all ./python -c "import shutil; print(shutil._BZ2_SUPPORTED)"
True
_bz2 у вас. А просто всегда возвращает True. Что делать - пока никто не знает: https://github.com/python/cpython/issues/150167wemake-python-styleguide: https://github.com/wemake-services/wemake-python-styleguide/issues/3639
Free-Threading и итераторы: что могло пойти так?
Недавно в питоне появилась новая фича (которую я вам пока не покажу), а я решил сделать новый формат – вопрос-загадку.
Мы все знаем, что Free-Threading работает совсем по-другому, вместо одного глобального GIL, у нас множество критических секций per-object и атомарных операций.
Тут обычный питоновский код на тредах. Создаем 10 тредов и идем по итератору, складываем его объекты в одну общую сумму с локом. В итоге должно получиться значение равное sum(range(limit)). Получится ли?
import threading
import time
from test.support import threading_helper
limit = 10_000
workers_count = 10
result = 0
result_lock = threading.Lock()
start = threading.Event()
def producer(limit):
for x in range(limit):
yield x
def consumer(iterator):
global result
start.wait()
total = 0
for x in iterator:
total += x
with result_lock:
result += total
iterator = producer(limit) # 🤔
workers = [
threading.Thread(target=consumer, args=(iterator,))
for _ in range(workers_count)
]
with threading_helper.wait_threads_exit():
for worker in workers:
worker.start()
for worker in workers:
# Wait for the worker thread to actually start.
while worker.ident is None:
time.sleep(0.1)
start.set()
for worker in workers:
worker.join()
Находки в опенсорсе: хлеб
> The sourdough framework is an open-source book dedicated to helping you to make the best possible sourdough bread at home.
https://github.com/hendricius/the-sourdough-framework
Наконец-то нормальные проекты!
Люблю домашний хлеб. Делимся рецептами вкусной еды / хлеба в комментах!
PEP-661: sentinel объекты
PEP: https://peps.python.org/pep-0661/
Код: https://github.com/python/cpython/pull/148831
Обсуждение: https://discuss.python.org/t/pep-661-sentinel-values/9126
В питон 3.15 добавляют новый builtin – sentinel, чтобы создавать значения по-умолчанию.
Проблема достаточно понятная, например: нам нужно создать какое-то значение по-умолчанию, чтобы мы знали, что аргумент не был передан. Но None является валидным значением в нашей логике. Потому нужно создать новое особое "пустое" значение.
Данная логика встречается буквально везде:
• dataclasses
• django_modern_rest
• msgspec (тоже самое но на C)
Однако, теперь можно упростить АПИ для создания таких объектов до:
_SENTINEL_VALUE = sentinel('_SENTINEL_VALUE')
dataclasses уже используют новое АПИ: https://github.com/python/cpython/commit/16952218d0535904236e8a39851133688c9ce1f0sentinel написан на C, его исходники вот тут: https://github.com/python/cpython/blob/main/Objects/sentinelobject.c
class sentinel:
__slots__ = ("__name__", "_module_name")
def __init_subclass__(cls):
raise TypeError("type 'sentinel' is not an acceptable base type")
def __init__(self, name, /):
if not isinstance(name, str):
raise TypeError("sentinel name must be a string")
self.__name__ = name
self._module_name = sys._getframemodulename(1)
@property
def __module__(self):
return self._module_name
def __repr__(self):
return self.__name__
def __reduce__(self):
return self.__name__
def __copy__(self):
return self
def __deepcopy__(self, memo):
return self
def __or__(self, other):
return typing.Union[self, other]
pickle должен корректно работать, для того имя sentinel('NAME') должно совпадать с именем объекта на уровне модуля: NAME = msgspec например): PyObject *PySentinel_New(const char *name, const char *module_name) для созданияbool PySentinel_Check(PyObject *obj) для проверки
Монадические выражения в Python. Наконец-то!
Продолжаем разговор про интересные PEPы. Поговорим сразу про два:
- PEP-823: https://peps.python.org/pep-0823
- PEP-824: https://peps.python.org/pep-0824
В целом они оба об одном: использовании ? для решения проблемы обращения к None при работе с данными.
Первый PEP описывает два новых оператора ?. и ?[], которые позволяют представить выражение some?.field как:
if some is not None:
field = some.field
else:
field = None
some?[field] будет делать тоже самое, но для для some[field].?? вместо is not None и тернарных выражений. Теперь мы сможем писать some ?? 'default' вместо some if some is not None else 'default'. И есть вариант ??= по аналогии с +=, тд.
def get_customer_name(data: Data) -> str | None:
"""Get customer name in lower case if it exists."""
customer = data.customer
if customer is not None:
user = customer.user
if user is not None:
return user.name.lower()
return None
def get_customer_name(data: Data) -> str | None:
return data.customer?.user?.name.lower()
data.customer является None, то мы сразу его возвращаем. Если нет, пробуем получить .user, проверяем его на None. И тд.__getattr__ и __getitem__ никак не меняются, они просто не вызываются при None.get_customer_name все еще возвращает str | None. к optional полю - все еще ошибка?. к optional полю - не ошибка, а просто Noneget_customer_name выполняет много опкодов на ровном месте.LOAD_ATTR и тд. Что делает данный код сложным для оптимизации и JIT'ирования. Но data.customer?.user?.name.lower() оптимизировать очень удобно, потому что можно сделать один новый опкод LOAD_OPTIONAL_ATTR (или добавить еще один oparg к текущему LOAD_ATTR), а уже его будет очень удобно оптимизировать. Потому что весь код проверок будет жить в одном месте рядом на C, а не куча условий на питоне. И последовательность таких LOAD_OPTIONAL_ATTR будет удобно находить и возможно оптимизировать в том числе.dry-python/returns уже много лет была доступна конструкция Maybe с методом .bind, который ведет себя прямо как ?. оператор и не продолжает выполнение после первого None / Nothing.dmr@0.16.0 почти готов; изменений просто капец сколько. Каждая часть фреймворка вылизывается и становится на порядок лучше. Релиз в конце #opensource_september
Большая бесплатная конференция в Нижнем Новгороде 17 октября
Регистрация: https://itgorky.ru/bolshaya-vstrecha
При поддержке @it52info
Мы продолжаем нашу традицию делать большую осеннюю конференцию в Нижнем.
Вот тут можно почитать / посмотреть как было в прошлом году: /channel/opensource_findings/934
Что будет?
- Много разных секций: Python / DevRel / Frontend / Mobille
- Слет всего нашего сообщества @pytho_nn: приедет весь наш опенсорс чат, будет кучу людей из Москвы, Казани, других городов
- Много дружеского и профессионального общения
- Настолки!
- Встреча подписчиков. Приедет много людей из телевизора, всех их мы заманим в бар после конференции, с ними можно будет поговорить, выпить пива, сфотаться, подружиться :)
- Бар, кальяны и тусовка до утра (самое главное, очевидно)
В текущем году я решил позвать в качестве докладчиков ваших любимых ютюберов и опенсорсеров.
Доклады
- 11:00 Иван Кирпичников, опенсорс разработчик Adaptix. Опыт в проде и какие приколюхи можно творить Посмотрим на конкурента Pydantic от нашего сообщества. Посмотрим: где и когда не стоит использовать Pydantic, какие у него актуальные проблемы. Поговорим, за счет чего Adaptix насколько быстрый, как он устроен. И какие уникальные фичи у него есть.
- 12:00 Егор Бугаенко, Huawei, @yegor256news, yegor256" rel="nofollow">https://youtube.com/@yegor256 Типичные ошибки начинающего опенсорс разработчика Проанализирую свой опыт работы с сотнями разработчиков в десятках опенсорс проектах, укажу на их ошибки, предложу альтернативы.
- 13:00 Алексей Гладких, Arenadata, @gaxeliy_tdd, https://twitch.tv/gaxeliy Зачем программисту Twitch (что?) Расскажу про недооценённую платформу для коммуникации, выстраивания горизонтальных связей и создания личного бренда. Покажу как можно стримить с технической стороны.
- 14:00 Александр Кондратьев, Ростелеком Modern Tests для Modern REST: современный подход к тестированию Django Modern REST Тесты API могут проверять разные уровни системы: логику контроллера, обработку HTTP-запроса, взаимодействие компонентов и соответствие реализации заявленному контракту. Если не разделять эти задачи, тесты становятся медленными и хрупкими, а высокое покрытие кода не гарантирует корректную работу API. На докладе поговорим о нашем опыте использования django-modern-rest в проде и организации тестов вокруг него: какие уровни проверок мы используем, что тестируем в запросах и ответах и какие инструменты помогают сократить рутинный код. Отдельно рассмотрим роль точной семантической схемы API: как на её основе генерировать тестовые сценарии, проверять реальные ответы и оценивать покрытие операций, параметров и вариантов ответа, а не только строк кода.
- 15:00 Максим Мельников, @pylounge, pylounge" rel="nofollow">https://youtube.com/@pylounge Я в СКРЫТОМ ПУЛЕ: выстраиваем процессы в команде без тильта Работа в IT-команде иногда подозрительно напоминает Dota 2: кто-то фармит, кто-то руинит, кто-то молчит до 40-й минуты, а тимлид начинает думать, что снова играет 1 vs 9. Только вместо Рошана - прод, вместо 0/10 мидера - соседняя CRM-команда, а вместо "gg ez" - очередной аварийный комитет. На докладе поговорим о том, как перестать играть в такой режим и начать выстраивать систему: почему токсичность и дизмораль только мешают, зачем учить людей говорить ртом и управлять ожиданиями, почему процессы не должны держаться на одном "рокстар-игроке" и как не превращать каждый фейл в поиск того, кто заруинил. Разберём, как продавать изменения команде, договариваться с бизнесом и другими командами, фиксировать договорённости и автоматизировать правила - чтобы вместо вечного 1 vs 9 получилась команда, которая действительно играет на победу как для бизнеса, так и технического развития проекта.
- 16:00 Хоренян Сурен, Яндекс Реклама, @Khorenyan, SurenKhorenyan" rel="nofollow">https://youtube.com/@SurenKhorenyan Агент пишет код. Я всё ещё программирую У кого-то ИИ забрал удовольствие от программирования; для меня это второе дыхание. Я смотрю на то, что пишет агент, а в голове только одна мысль: "я сделаю лучше". Разберём архитектурные странности в Python-коде от агентов: что можно упростить, переделать или с удовольствием удалить. Для разработчиков, которые пишут с агентами, ревьюят код от агентов и хотят понимать, что вообще происходит с кодовой базой.
А еще будут и другие треки по другим направлениям, их анонсы будут в их сообщества Нижнего и на @it52info.
Нужна только регистрация: https://itgorky.ru/bolshaya-vstrecha
Приходите! Приезжайте! Буду рад всех видеть лично. Хорошо и интересно проведем время.
Поддержать проект
Даже если вы не сможете присоединиться, нас можно поддержать. От сообщества - для сообщества.
Сделайте репост своим коллегам в чатик, закиньте в ваше локальное сообщество питонистов, поделитесь в своем тг канале. Будем рады любой медийной поддержке!
Обсуждение: Что вы реально думаете про текущее состояние платных конференций? Есть ли в них еще хоть какой-то смысл? Я вот думаю, что не особо.
| Поддержать | sobolevn">YouTube | GitHub | Чат |
Как AWS у опенсорсера пакеты отжимал
Нерегулярная рубрика "посмотрите, что творится!". Данная история была в оригинале рассказана Кареном в нашем чате (там регулярно происходит интересное), публикую в своем канале с его разрешения.
Новый SDK для AWS от Карена (автора zapros, автора httpx-aiohttp, топ-3 контрибьютора httpx): https://github.com/kap-sh/capo
Как-то я решил написать нормальный SDK для AWS. Их текущий SDK — boto3 — это суперлегаси с кучей костылей: без нормальной типизации, без поддержки асинхронности, почему-то с PascalCase и ещё кучей странных решений.
У меня уже был хороший опыт работы с SDK: до этого я работал над SDK для OpenAI и Anthropic на Python и TypeScript, так что успел накопить некоторое понимание того, как делать SDK хорошо.
С AWS всё немного сложнее: у них около 450 сервисов и примерно 17 миллионов строк сгенерированного кода. Конечно, я не собирался писать всё это вручную. Вместо этого я пишу кодогенератор, который генерирует SDK из спецификаций Smithy (язык и тулинг для спецификаций). Примерно такой же подход я использовал, работая над SDK для OpenAI и Anthropic.
И, конечно, я не стал делать это одним пакетом на PyPI. Прикиньте, устанавливать 17 миллионов строк кода только для того, чтобы создать S3-бакет.
Поэтому я делаю отдельный пакет для каждого сервиса.
Для всех пакетов я выбрал префикс aws-sdk: aws-sdk-s3, aws-sdk-ecs и так далее. Успешно опубликовал около 60 сервисов на PyPI.
А на следующий день до меня достучались ребята из AWS.
Они заметили мою работу, пореспектовали, но сказали, что именно эти имена они сами давно хотели использовать для своего нового SDK — замены boto3. И попросили вернуть им эти имена.
И тут есть ещё одно интересное совпадение: в тот же день PyPI заморозил мой аккаунт. В заморозке он продержался больше месяца. Причины мне так и не объяснили, но, думаю, каждый может сделать свои выводы 🙂
Я не стал с ними бороться и согласился вернуть имена.
left-pad начиналась так же.capo:
from capo_s3 import AsyncS3Client
async def main():
async with AsyncS3Client() as s3:
response = await s3.create_bucket("capo")
print(response)
from capo_s3 import S3Client
with S3Client() as s3:
response = s3.create_bucket("capo")
print(response)
zapros, как HTTP фреймворк для запросов.TypedDict, все ответы типизированы, но с 0 дополнительных кастов. Но типизация все еще помогает понять, какие ответы от каких сервисов можно использовать как входные данные для других, а где - так сделать будет нельзя.boto3?
Я попробовал uv, обплевался и вернулся на poetry
Если вы последние полгода не читали чат, то вы не знаете, как сильно у меня горело после перехода на uv. Сейчас расскажу! Данный пост специально написан, чтобы и у вас тоже сгорело. Если к концу вы подойдете без боли в ... душе, то все было зря.
Как все было?
Обычно, я первый прыгаю пробовать все новые и модные штуки.
Так было с Pipenv, когда он только появился. Что там творилось!
Так было с poetry, которым я начал пользоваться через год, в 2018м.
Но вот с uv так не было, потому что я не знал, зачем бы мне хотелось переехать. Быстрее?
Но ладно, мы решили попробовать его на 3х проектах. Быстро же! Ух 🌚
- https://github.com/typeddjango/django-stubs (терпим)
- https://github.com/wemake-services/django-modern-rest (сложно съехать)
- https://github.com/wemake-services/wemake-django-template (откатили)
Почему?
Ох, даже не знаю с чего начать! Я напишу только про то, с чем столкнулся лично.
- У uv нет своей системы сборки для бинарников, uv_build умеет собирать только Python пакеты. DMR компилирует часть своих исходников, нам приходится доставлять hatch (другой пакетный менеджер), чтобы вообще иметь возможность работать. Топ кек
- Полное игнорирование стандартов. PEP 751? pip.conf? Нет, не слышали.
- uv публиковал нам сломанные README в PyPI, пришлось ставить плагин
- uv ставит вам совсем не тот питон, который вы думаете. pyenv ставит вам ровно то, что вы попросили, то uv тянет "relocatable" сборку: питон собранный на одной платформе для других платформ. Будет ли все работать? Конечно же нет. Сборка с ним пакетов локально просто не работает. Разбираться в C стектрейсе я не стал, просто поставил нужный питон через pyenv
- Сломанные дефолты. uv run автоматически ставит зависимости, что скрывало от нас баг в CI (нужно ставить UV_NO_SYNC=1. uv sync создает виртуально окружение без pip (нужно указывать UV_VENV_SEED=1), из-за чего pip install package ставит пакет в глобальный pip, даже с включенным venv. Ну то есть: вирутальное окружение начинает протекать :(
- Для шаблонов uv не подходит, потому что добавляет имя пакета в uv.lock (даже когда ты явно говоришь, что сам пакет ставить не надо), если оно меняется, то лок разваливается. Нам пришлось убрать параметризацию имени пакета в шаблоне. Потом откатили
- uv записывает текущую версию проекта (не пакета) в uv.lock, что ломает релиз тулы вроде semantic-release
- uv add package добавляет его в dependencies как "package>=current.version", что ломает будущие вызовы uv sync -U, когда к нам прилетают ломающие изменений из новых мажорных версий пакетов
- Нет возможности посмотреть устаревшие пакеты как в poetry show --outdated, есть внешний плагин
- Не работает uv sync без pyproject.toml, что нужно для кеша докер слоев. Уже джва года
- uv допускает дубликаты зависимостей с разными версиями (!), что вызвало у нас странный баг
- uv self update не работал в punq из-за наличия tox-uv в зависимостях
- uv не работает с dependabot, если используется workspace, пришлось переходить на renovate. PRов просто не было. uv sync -U с workspaces крайне плохо работает, какие-то пакеты приходилось руками обновлять
- Для продвижения ty добавили более длинный алиас uvx ty: uv check
Ну и самое главное: теперь uv принадлежит OpenAI. Можно ли им доверять важную часть инфраструктуры? Я сильно сомневаюсь.
Получил ли я что-то взамен на все мои новые страдания? Пример из wemake-django-template:
- poetry: 20 секунд
- uv: 11.4 секунды
Я понимаю, что для каких-то больших проектов разница может быть значительной.
Использовать? Если вы пофиксите дефолты у себя в конфиге, у вас стандартный проект, у вас есть линтер на номера версий, бот для обновления зависимостей, и вы реально замечаете время установки пакетов локально / в CI с другими тулами.
Ключевой вопрос: может стоило просто переписать резолвер и скачивание пакетов в poetry на rust?
Обсуждение: Какой ваш опыт? Заметили ли вы, что последнее время инструменты хайпят только за счет "скорости"?
Вайбкодим security компоненты в django-modern-rest (или нет)
https://www.youtube.com/watch?v=laJQcNAqxc0
В django-modern-rest появилась большая задача: полностью мигрировать в себя dj-rest-auth.
Ссылочка (мы ищем контрибьюторов, особенно специалистов в безопасности OAuth / MFA / Passkeys!): https://github.com/wemake-services/django-modern-rest/issues/1193
На что @fastnewsdev (как главный пропагандист ИИИ = "инженерный искусственный интеллект" ) сказал, что он мне такое заваншотит клодом еще до того, как я успею выпить бутылку пива.
Ну вот и посмотрим, пиво я пью быстро 🌚
Завтра стрим в 19:00 на канале вялых питонов:
- Никита будет вайбкодить Django код (он не знает джангу)
- Никита будет ревьюить и пытаться дотащить код хоть до какого-то вменяемого состояния
Приходите смотреть, на что реально способен ИИ. Сбивайте нас с толку своими остроумными комментариями, задавайте провокационные вопросы.
Обсуждение: На какой результат ставите? Что в итоге получится? За какого Никиту будете болеть лично вы? Сколько Никит будет на стриме?
| Поддержать | sobolevn">YouTube | GitHub | Чат |
Внутри питона есть ЕЩЕ виртуальные машины
Мы все знаем, что сам питон - одна большая стековая виртуальная машина, которая выполняет опкоды. Их мы можем посмотреть через dis:
>>> import dis
>>> dis.dis('x + y')
0 RESUME 0
1 LOAD_NAME 0 (x)
LOAD_NAME 1 (y)
BINARY_OP 0 (+)
RETURN_VALUE
>>> class User:
... def __init__(self, username: str, tags: list[str]) -> None:
... self.username = username
... self.tags = tags
... def __reduce__(self) -> tuple[type['User'], tuple[Any, ...]]:
... return (type(self), (self.username, self.tags))
>>> import pickle
>>> user = User('sobolevn', tags=['python', 'tg'])
>>> pickle.dumps(user, protocol=0)
b'c__main__\nUser\np0\n(Vsobolevn\np1\n(lp2\nVpython\np3\naVtg\np4\natp5\nRp6\n.'
>>> pickle.dumps(user, protocol=1)
b'c__main__\nUser\nq\x00(X\x08\x00\x00\x00sobolevnq\x01]q\x02(X\x06\x00\x00\x00pythonq\x03X\x02\x00\x00\x00tgq\x04etq\x05Rq\x06.'
0 до pickle.HIGHEST_PROTOCOL для ваших объектов, которые поддерживают такой способ сериализации.
>>> import pickletools
>>> pickletools.dis(pickle.dumps(user, protocol=1))
0: c GLOBAL '__main__ User'
15: q BINPUT 0
17: ( MARK
18: X BINUNICODE 'sobolevn'
31: q BINPUT 1
33: ] EMPTY_LIST
34: q BINPUT 2
36: ( MARK
37: X BINUNICODE 'python'
48: q BINPUT 3
50: X BINUNICODE 'tg'
57: q BINPUT 4
59: e APPENDS (MARK at 36)
60: t TUPLE (MARK at 17)
61: q BINPUT 5
63: R REDUCE
64: q BINPUT 6
66: . STOP
highest protocol among opcodes = 1
>>> pickletools.opcodes[25].code
']'
>>> pickletools.opcodes[25].doc
'Push an empty list.'
pickle для более быстрой сериализации / десериализации. pickle сериализовать любой Python объект (почти), а с помощью других средств - получается сильно сложнее.pickle сам по себе? Зачем нужны протоколы и версии? Или сделать отдельный пост про детали работы? Знаете ли вы, что pickle - фундаментально небезопасный протокол? И нельзя запускать чужие дампы, только свои доверенные?
Наконец-то полезные фичи в питоне
Многие знают, что я почти не добавляю новые фичи в CPython, я стараюсь выпилить существующие и править баге в тех, что у меня не получается убирать. Новые если и добавляю, то без масштабных обсуждений.
Некоторые новые фичи встречают у меня сильный оптимизм: как новый встроенный sampling profiler. Вот тут видео про него кстати с прошедшего PyCon.
Некоторые фичи встречают у меня понимание: как например typing.disjoint_base. Простая штука, решает понятную проблему.
Некоторые фичи встречают у меня лютое подгорание: как например lazy imports. Вот доклад с пайкона и про них, кстати.
Я думаю, что мое понимание хорошей фичи очень сильно расходится с таким пониманием у других питонистов, мое понимание "хорошего питона" можно найти в моем wemake-python-styleguide.
Зная такую вводную, я решил сделать "большую" новую фичу. На один символ в грамматике.
Иммутабельному питону - быть!
У нас была довольно большая проблема: создавать мутабельные словари и множества - можно довольно легко. {1: 2} и {1, 2, 3}
Чтобы создать frozendict и frozenset нам уже нужен вызов функции: frozendict({1: 2}) и frozenset({1, ,2, 3}). Почему так делать не очень?
1. Потому что писать долго, мало людей будут заморачиваться. Зачем, когда проще создать мутабельную структуру?
2. Потому что frozendict и frozenset тупо медленнее. frozendict пока вообще имеет 0 оптимизаций для работы и просто в тупую копирует всю память из dict, который мы отправляем. Получая буквально O(n * 2) по памяти и времени работы. Делает лишний CALL. А frozenset({1, 2, 3}) немного оптимизирован через INTRINSIC_BUILD_FROZENSET опкод, который генерируется только для set в качестве входного аргумента
3. Неудобно писать comprehensions. Они получается сильно менее читаемые, чем их мутабельные версии
Мое предложение (пока только в формате обсуждения): https://discuss.python.org/t/frozenset-and-frozendict-comprehensions/101584/9
Мой PR с добавлением данной фичи в CPython: https://github.com/python/cpython/pull/152820 (он нужен для написания ПЕПа)
Как оно выглядит?
>>> ${1: 2}
frozendict({1: 2})
>>> ${1, 2, 3}
frozenset({1, 2, 3})
>>> ${x: x for x in range(3)}
frozendict({0: 0, 1: 1, 2: 2})
>>> ${x for x in range(2)}
frozenset({0, 1})
$:${} - пустой frozendict, как {} - пустой dict${1, *other} - распаковка внутри frozenset${**d for d in list_of_dicts} - распаковка внутри frozendict comprehension + PEP-798${x async for x in async_iterable if x >0} - async frozenset comprehension с условием$? $ - is the real deal 😎💸$ не имеет смысла сейчас: но будет значить "иммутабельность"$ может легко в дальнейшем использовать для других иммутабельных штуках: $(x for x in range(1)) для нативного tuple comprehension, для PEP-805 с __freeze__ и тдmatch/case в wemake-python-stylguidepunq с поддежкой типизации
Если вы понимаете данный баг, то вы знаете питон лучше 95% людей
А если нет, то вы многое узнаете про то, как работает память и почему мутабельностью стоит пользоваться с осторожностью.
Недавно я увидел один из лучших багов в CPython за долгое время. А я видел много багов 🌚️️
Вот код, который делает две критичные безумные вещи (попробуйте их найти прежде, чем читать дальше):
class Evil:
def __eq__(self, other):
return other
leaked = vars(list) == Evil()
name = "example"
leaked[name] = lambda self: "probe"
print(getattr(list, name)([]))
del leaked[name]
print(hasattr(list, name))
list, хотя такое должно быть невозможноEvil, который просто возвращает из __eq__ второй объект, который ему передали. Так можно делать, тут нет ничего сломанного.vars(list) с Evil, и вот тут как раз в leaked попадет второй объект из Evil.__eq__, в нашем случае vars(list)list.__dict__, который является не обычным dict, а types.MappingProxyType, то есть иммутабельным маппингом поверх оригинального значения. Добавлять в него ключи нельзя. Потому что мы не хотим, чтобы в список или другие типы нам подкидывали какие-то новые методы во время работы программыmappingproxy? mappingproxy хранит в себе оригинальный мутабельный словарь, который он "проксирует" или "защищает от изменений". И сравнивает на самом деле не себя, а оригинальный объектlist.__dict__ мы получаем PyDictProxy_New(self->tp_dict), где хранится тот самый настоящий и защищенный __dict__ из типа list, который обычно не доступен вне C кодаmappingproxy разворачивается и достает из себя ->mapping, тот самый чистый и мутабельный ->tp_dict->tp_dict, мы можем в него добавлять методы: leaked[name] = lambda self: "probe". Они будут работать. Мы только что достигли пункта 1. и мутировали встроенный Python тип без единого импортаdel leaked[name]hasattr(list, name) крашится вот тут на обращении к уже освобожденной памяти. EXC_BAD_ACCESS, пункт 2. пал
if (
PyDict_CheckExact(v->mapping) &&
!(PyAnyDict_CheckExact(w) || PyODict_CheckExact(w))
) {
// So, instead we send a copy:
PyObject *copy = PyDict_Copy(v->mapping);
if (copy == NULL) {
return NULL;
}
PyObject *res = PyObject_RichCompare(copy, w, op);
Py_DECREF(copy);
return res;
}
Почему msgspec такой быстрый?
Несколько дней назад я решил разобраться в устройстве msgspec. Получилось како бычно: я напал на него со своими PRами, мне через день выдали права на merge и release. Но самое главное: теперь я могу рассказать вам про внутреннее устройство самого быстрого сериализатора для json в питоне.
Как быстро распарсить json?
Традиционные парсеры json делают так:
- Парсим весь json документ
- Используем промежуточный слой для хранения json как примитивных Python объектов: dict, list, int, str, None, тд
- Превращаем Python объекты в финальный вариант: датаклассы, модели, более сложные типы, тдmsgspec использует несколько важных хитростей, чтобы парсить json наиболее быстрым способом.
Пример:
>>> import msgspec
>>> class User(msgspec.Struct):
... username: str
... email: str
...
>>> decoder = msgspec.json.Decoder(User)
>>> decoder.decode(b'{"username": "example", "email": "email@example.com"}')
User(username='example', email='email@example.com')
TypeNode *type для мета информации о том, что мы будем парсить. В нашем случае там будет struct User с двумя str полями
static MS_INLINE PyObject *
json_decode_nocustom(
JSONDecoderState *self, TypeNode *type, PathNode *path
) {
// ...
switch (c) {
case 'n': return json_decode_none(self, type, path);
case 't': return json_decode_true(self, type, path);
case 'f': return json_decode_false(self, type, path);
case '[': return json_decode_array(self, type, path);
case '{': return json_decode_object(self, type, path);
case '"': return json_decode_string(self, type, path);
default: return json_maybe_decode_number(self, type, path);
}
}
type у нас MS_TYPE_STRUCT и будет парсить сразу msgspec.Struct. Что еще более хитро, то парситься будут только те ключи, которые явно указаны в User, остальные будут просто пропускаться через вызов json_skip.char *, чтобы сравнить его с существующими ключами User. Но вот создавать дорогие промежуточные Python объекты мы не будем. Если ключ нам не нужен, то и значение его мы парсить не будем. На выходе получим сразу объект User без промежуточных слоев и их аллокаций. Быстро? Быстро.msgspec есть главный минус: плохая поддержка Union типов. То есть: некоторые комбинации данных вообще не получится распарсить. Например: str | bytes. Или два датакласса. Или два тайпдикта. Почему? Потому что оптимизации пока мешают работе 🌚pydantic умеет куда больше. Потому я в django-modern-rest и сделал выбор сериализатора для каждого отдельного контроллера. Чтобы точечно выбирать скорость vs функциональность.pyrefly, heap types, поддержку subinterpreters, FT, более гибкие правила проверок значений и тд.frozendict для Python 3.15+ и предложил сделать новое АПИ для него: PyFrozenDict_FromDictSteal, потому что текущее АПИ работает за O(n * 2), когда можно за O(n).
Значимые и незначимые пробелы в Python
Во время стрима я решил, что сейчас у меня будет приключение на 15 минут, что я быстренько запилю новую синтаксическую ошибку.
В чем суть?
Довольно легко опечататься и написать вместо корректного lazy from os import path неправильную форму from os lazy import path. На что человек просто получит голый SyntaxError без подсказок и советов. Оно работает, но DX не самый лучший для новой фичи. Особенно, учитывая тот факт, что from os lazy import path выглядит консистентно с lazy import os.
И первая часть задачи у меня получилась прямо на стриме. Теперь from os lazy import path выдает красивую ошибку:
>>> from os lazy import path
File "<python-input-0>", line 1
from os lazy import path
^^^^
SyntaxError: use 'lazy from ... ' instead of 'from ... lazy import'
from . lazy import name у меня сразу не вышла. На стриме оч сложно программировать. Я, честно сказать, сначала растерялся. А потом понял: в питоне есть значимые пробелы: например для идентации кода. Они превращаются в токен INDENT. a+b и a + b - одинаковый код. Что на самом деле ведет к чудовищам вида:
>>> 1. .real
1.0
>>> 1if True else 0
<python-input-2>:1: SyntaxWarning: invalid decimal literal
1
>>> [1.0for _ in range(1)]
<python-input-3>:1: SyntaxWarning: invalid decimal literal
[1.0]
from . lazy import x и from .lazy import x - ОДИН И ТОТ ЖЕ КОД.x из модуля lazy.lazy from, а не from ... lazy import.SyntaxWarning:
>>> from . lazy import x
<python-input-0>:1: SyntaxWarning: 'from . lazy import' is the same as 'from .lazy import'; did you mean 'lazy from . import'?
Анонс стрима: "работаем над lazy import'ами в CPython и плачем под аниме"
(на превью - я на стриме)
Мы с @nkhitrov_blog, @fastnewsdev и Денисом Аникиным (в 2026 и без тг канала!) решили замутить стрим по питону и ... новый канал на ютюбе под названием "Вялые Питоны".
Подписаться уже можно вот тут: SluggishPythons" rel="nofollow">https://www.youtube.com/@SluggishPythons
О чем будет канал?
- Менее душный и более мемный чем мой основной
- Все еще про питон и всякие хардкорные штуки внутри
- Шутки, пиво, лень, слезы
- Разные новые форматы, которые мы будем анонсировать постепенно
- Разные интересные коллабы с веселыми и умными людьми
Контент на старом канале останется таким же, каким и был. Я как раз вернулся из творческого отпуска. Скоро будет завоз по adaptix и django-modern-rest. И финал по vscode.
О чем будет первый стрим?
- Обсудим мотивацию и устройство PEP-810, потестим разные странные случае, Никита побомбит
- Я запилю каких-нибудь пару тасочек в CPython, например https://github.com/python/cpython/issues/150459
- Если я буду плохо рассказывать, что там происходят - пацаны будут меня душить своими любимыми аниме
- Если хватит времени, то еще починим setuptools / distutils, а то я все сломал
- Выпьем пива со всеми желающими 🍻
Народ в чате проголосовал за время стрима в будний вечер, так что - записываем дату и время:
Среда, 3 июня, 19:00
https://www.youtube.com/watch?v=W9Hd5dfxjIU
Приходите задавать свои ответы и хорошо проводить время!
Настало время ответов!
Во-первых
Код из примера упадет с ValueError: generator already executing.
Почему так? Нельзя дважды запустить один и тот же генератор, даже в одном треде.
Самый простой пример:
def g():
i = next(me)
yield i
me = g()
next(me) # ValueError
static PySendResult // Objects/genobject.c
gen_send_ex(PyGenObject *gen, PyObject *arg, PyObject **presult)
{
int8_t frame_state = FT_ATOMIC_LOAD_INT8_RELAXED(gen->gi_frame_state);
// ...
if (frame_state == FRAME_EXECUTING) {
PyErr_SetString(PyExc_ValueError, "generator already executing");
return PYGEN_ERROR;
}
// ...
}
__next__ под локом (как правильно догадались в комментариях):
class serialize_iterator:
def __init__(self, iterable):
self._iterator = iter(iterable)
self._lock = Lock()
def __iter__(self):
return self
def __next__(self):
with self._lock:
return next(self._iterator)
iterator = producer(limit) на iterator = threading.serialize_iterator(producer(limit)). Есть еще декоратор @synchronized_iterator для определения threadsafe генераторов сразу.
ИИ переписал Bun с Zig на Rust
PR: https://github.com/oven-sh/bun/pull/30412 (он настолько большой, что гитхаб его не открывает у меня)
Последние несколько дней в чате очень плотно обсуждали последнюю ИИ новость.
Один из альтернативных JS рантаймов bun полность переписали с zig на #rust.
Переписывали, конечно же, используя исключительно агентов и ИИ (от компании Anthropic) .
На все про все ушло 10 дней, тесты прошли, перформанс остался такой же.
Звучит красиво? Красиво.
Таймлайн истории
1. 2 декабря 2025 года Anthropic покупает bun и всю команду: https://bun.com/blog/bun-joins-anthropic
2. Команда Zig известна своим "No AI Slop" policy (прямо как django-modern-rest), некоторые люди сразу предсказывали конфликт интересов между Bun + Anthropic и Zig
3. 26 апреля 2026 года, команда bun форкает zig и добавляет туда поддержку параллельного семантического анализа https://x.com/bunjavascript/status/2048427636414923250
4. 9 мая открывается тот самый PR
5. 14 мая он успешно смерджен
Важные детали
А вот тут начинается интересное.
- Для начала авторы Zig объяснили, что подход форка с семаналом некорректный, и что они сами работают над данной фичей, скоро она будет доступна: https://ziggit.dev/t/bun-s-zig-fork-got-4x-faster-compilation-times/15183/19
- Билды получились недетерминированные, о чем им и рассказала кор-команда. Тогда форк пришлось закопать, видимо
Теперь посмотрим на качество PR.
- Качество кода там примерно вот такое: https://github.com/oven-sh/bun/commit/d144fa6e20ab65d55add82ef3241609dcbb04cdc (то есть - никакое)
- Файлы в нем даже были неотформатированы встроенным cargo fmt, что делается буквально в каждом Rust проекте: https://github.com/oven-sh/bun/pull/30695
- Ревью не было, потому что внутри PRа +1 009 257, -4 024 и 6000+ коммитов
- unsafe в коде встречает 10487 раз (да, там много ffi, но все равно). Для сравнения в uv (кода правда меньше в 2 раза) - всего 73 раза
- "Скорость работы осталось такой же" - довольно странный тезис, учитывая что zig и rust оба генерят код через LLVM, часто практически идентичный, заслуги ИИ здесь нет
Выводы
- Прикольно, что такое вообще можно сделать (с неограниченными токенами)
- Как теперь bun будет владеть своей базой кода, кто сможет в ней разобраться и что-то пофиксить - вопрос открытый
- Какой смысл во всем действии (кроме очевидного маркетинга) - вопрос открытый
- Брать ли теперь bun в прод? Конечно нет
Обсуждение: что вы думаете по данному вопросу? Стали бы использовать bun у себя в проекте в новом виде?
| Поддержать | sobolevn">YouTube | GitHub | Чат |
Вышел mypy 2.0
Changelog: https://github.com/python/mypy/blob/master/CHANGELOG.md#mypy-20
Что изменилось?
По-умолчанию --local-partial-types теперь всегда включен. Он нужен для корректной типизации в разных скоупах.
a = [] # Needs type annotation when using `local-partial-types`
def func() -> None:
a.append(1)
--strict-bytes по-умолчанию. Раньше тип bytes разрешал передавать memoryview и bytearray. Теперь с новым поведением bytes разрешает только bytes, все остальные типы нужно указывать отдельно.--allow-redefinition
def foo(cond: bool) -> None:
if cond:
for x in ["a", "b"]:
# Type of "x" is "str" here
...
else:
for x in [1, 2]:
# Type of "x" is "int" here
...
--allow-redefinition-new, а теперь включена по-умолчанию.--num-workers, который позволяет ускорить mypy кратно на больших кодовых базах. Я буду запускать mypy прямо на кодовой базе mypy (без mypyc, без кеша, но с orjson и `sqlite_cache`):
» rm -rf .mypy_cache && time mypy --config-file mypy_self_check.ini -p mypy -p mypyc --num-workers=8
7.090 total
» rm -rf .mypy_cache && time mypy --config-file mypy_self_check.ini -p mypy -p mypyc
25.335 total
orjson и sqlite_cache:
» rm -rf .mypy_cache && time mypy --config-file mypy_self_check.ini -p mypy -p mypyc
28.108 total
mypyc (то есть та, которую мы скачиваем из pip) будет еще быстрее.
Нерегулярная рубрика "посмотрите, что творится!". Как вы знаете, рынок найма http клиентов полностью сломан! Сегодня мы постараемся решить данную проблему.
zapros - modern and extensible python http client
Звезды ставить сюда: https://github.com/kap-sh/zapros
Документация: https://zapros.dev
Сообщество: @pythonzapros
Недавно мне написал Карен Петросян (кстати, заходите к нам в чат, где все события и происходят) – топ3 мейнтейнер библиотеки HTTPX по количеству коммитов, автор httpx-aiohttp и hishel. И говорит: я сделал новый крутой клиент для HTTP для питона. И я такой: офигеть! Дайте два!
В чем фишка?
А ситуация на рынке такова. requests морально устарел 10 лет назад. На фоне умирающего HTTPX, у которого не было релиза больше года, и автор которого не хочет релизить новые версии и даже заблокировал возможность создавать новые задачи, автор Zapros попытался написать аналог, способный не только заменить HTTPX, но и предложить кучу новых интересных фич.
from zapros import AsyncClient
async def main() -> None:
async with AsyncClient() as client:
response = await client.get("https://httpbin.org/get")
print(response.status, response.json)
Zapros - его дизайн: вместо того чтобы зависеть от конкретных имплементаций транспортного уровня, Zapros работает с абстракциями, благодаря которым он может поддерживать:Zapros поверх любых транспортных реализаций.Zapros имеет всего лишь 3 зависимости: h11, pywhatwgurl и typing-extensions. Поддерживает Python 3.10 и выше.Zapros был спроектирован с удобным механизмом расширения клиента с помощью миддлварей. Из коробки идут миддлвари для:
from zapros import CacheMiddleware, Client, RetryMiddleware
with (
Client().wrap_with_middleware(
lambda next: RetryMiddleware(next) # wrap with the retry middleware
).wrap_with_middleware(
lambda next: CacheMiddleware(next) # wrap with the cache middleware
) as client
):
# automatically retries failed requests and caches responses
client.get("https://zapros.dev")
Zapros не принуждает использовать ни одну из данных миддлварей: сам класс клиента отвечает только за отправку HTTP-запросов, всё остальное — уже миддлвари, которые вы можете использовать по своему усмотрению. И хотя основные миддлвари написаны так, чтобы покрывать большинство случаев использования, вы можете использовать и свои кастомные решения.Zapros поддерживает как синхронный, так и асинхронный интерфейс, и использует улучшенную версию механизма unasync, который используется в httpx для поддержки обоих интерфейсов.