Находки в опенсорсе
Привет! Меня зовут Никита Соболев. Я занимаюсь опенсорс разработкой полный рабочий день. Тут я рассказываю про #python, #c, #opensource и тд. Поддержать: https://boosty.to/sobolevn РКН: https://vk.cc/cOzn36 Связь: @sobolev_nikita
Показати більше📈 Аналітичний огляд Telegram-каналу Находки в опенсорсе
Канал Находки в опенсорсе (@opensource_findings) у мовному сегменті Англійська є активним учасником. На даний момент спільнота об'єднує 11 595 підписників, посідаючи 10 626 місце в категорії Технології та додатки та 56 000 місце у регіоні Росія.
📊 Показники аудиторії та динаміка
З моменту свого створення невідомо, проект продемонстрував стрімке зростання, зібравши аудиторію у 11 595 підписників.
За останніми даними від 26 липня, 2026, канал демонструє стабільну активність. Хоча за останні 30 днів спостерігається зміна кількості учасників на 115, а за останні 24 години на -3, загальне охоплення залишається високим.
- Статус верифікації: Не верифікований
- Рівень залученості (ER): Середній показник залученості аудиторії становить 52.46%. Протягом перших 24 годин після публікації контент зазвичай збирає 25.65% реакцій від загальної кількості підписників.
- Охоплення публікацій: В середньому кожен допис отримує 6 083 переглядів. Протягом першої доби публікація в середньому набирає 2 974 переглядів.
- Реакції та взаємодія: Аудиторія активно підтримує контент: середня кількість реакцій на один пост – 142.
- Тематичні інтереси: Контент зосереджений навколо ключових тем, таких як pyobject, питон, slots, gil, github.
📝 Опис та контентна політика
Автор описує ресурс як майданчик для висловлення суб'єктивної думки:
“Привет!
Меня зовут Никита Соболев. Я занимаюсь опенсорс разработкой полный рабочий день.
Тут я рассказываю про #python, #c, #opensource и тд.
Поддержать: https://boosty.to/sobolevn
РКН: https://vk.cc/cOzn36
Связь: @sobolev_nikita”
Завдяки високій частоті оновлень (останні дані отримано 27 липня, 2026), канал підтримує актуальність та високий рівень охоплення публікацій. Аналітика показує, що аудиторія активно взаємодіє з контентом, що робить його важливою точкою впливу в категорії Технології та додатки.
django-modern-rest@0.5.0 потребовалось компилировать части фреймворка с mypyc для получения перформанса на ровном месте - мне пришлось разобраться. Так давайте и вам расскажу.
Как оно работает?
Пакет - достаточно простой, обычная клиха (украл слово у @diementros). При запуске - указываем какой wheel нужно собрать. Например:
cibuildwheel --only cp313-macosx_arm64 --config-file pyproject.toml
Соберет вам текущий пакет для 3.13 и macos с arm64 архитектурой. А вот конфигурация:
[tool.cibuildwheel]
build = "cp3{11,12,13,14}-*"
build-frontend = "uv"
test-command = 'your_test_command'
Как будет проходить сборка? Полный лог: https://github.com/wemake-services/django-modern-rest/actions/runs/24023507014/job/70057082696#step:4:195
1. Сначала устанавливается нужный питон из готовых образов
2. Подготавливаем окружение
3. Запускаем build систему. Она берется из вашего pyproject.toml / setup.py
Например, у нас она выглядит так (мы используем uv, у которого нет родной билд системы для бинарных зависимостей, потому используем `hatch`):
[build-system]
requires = ["hatchling", "hatch", "hatch-fancy-pypi-readme"]
build-backend = "hatchling.build"
Вторая часть задачи: запустить сам mypyc на нужных файлах. К hatch есть плагинчик hatch-mypy. Его надо тоже настроить:
[tool.hatch.build.targets.wheel.hooks.mypyc]
enable-by-default = false
dependencies = ["hatch-mypyc", "mypy==1.19.1"]
include = ["dmr/_compiled"]
require-runtime-dependencies = true
Тут мы указываем: что билдим, что по-умолчанию билд с mypyc выключен, какие зависимости для билда нужны и что нужно поставить рантайм зависимости для билда. Билдить с mypyc будем только если есть специальный флаг:
[tool.cibuildwheel.environment]
HATCH_BUILD_HOOKS_ENABLE = "1"
Только когда он есть (или мы билдим с cibuildwheel`), то сборка пакета запустится. Такое нужно нам, чтобы иметь возможность делать нативные python-only сборки без .so` частей.
4. Запускаем тесты собранного wheel пакета с test-command, проверяем, что собранный пакет работает
5. Замеряем, что наши скомпилированные части реально стали работать быстрее
Готово!
Запускаем в CI
Последняя часть: нужно как-то запустить CI с 50+ разных вариантов конфигураций. cibuildwheel тут снова поможет. Он умеет выплевывать такие конфигурации для CI командой: CIBW_BUILD="cp313-*" cibuildwheel --print-build-identifiers --platform macos.
Далее дело техники, собираем матрицу всех задач для нужной CI и запускаем такую матрицу:
mypyc:
name: mypyc wheels ${{ matrix.only }}
needs: configure
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include: ${{ fromJson(needs.configure.outputs.include) }}
Самая хитрая часть тут в include: там мы как раз динамически подставляем конфигурации от cibuildwheel.
Получается удобно и довольно просто.
Последним шагом мы просто загружаем данные пакеты при release, используя PyPI Trusted Publisher.
И вот так - к вам приехал новый релиз django-modern-rest с опциональными бинарными частями для СКОРОСТИ: https://github.com/wemake-services/django-modern-rest/releases/tag/0.5.0
Анонс митапа в Нижнем
Кстати, у нас скоро будет PythoNN митап в Нижнем Новгороде со всеми вашими любимыми спикерами: @diementros @pymineral, а еще Роман Фролов и Михаил Васильев.
17 апреля, начало в 18:30.
Регистрация: https://pytho-nn.timepad.ru/event/3880099
Приезжайте, приходите. Будет много пива, настолок, разговоров про питон.
Обсуждение: чем вы билдите колеса на работе? Нужно ли вообще такое где-то, кроме опенсорса?
| Поддержать | YouTube | GitHub | Чат |AGENT.md
• Использовать последние SOTA модели
• Пользоваться скилами готовыми под нужные технологии
Еще я заметил, что некоторые языки подходят лучше, чем другие.
Пока остановился на Go. Язык очень приятно выглядит. Он простой, но выразительный.
Из-за его продвинутой статической типизации и универсальности писать на нем большие проекты будет очень удобно.
Как быстро меняется мир!
Обсуждение: В комментах посоветуйте своё любимое аниме? Смотреть нечего!
Хорошего праздника! #ironid: c435ff72
| Поддержать | YouTube | GitHub | Чат |0.4.0 https://github.com/wemake-services/django-modern-rest
И там мы выпустили поддержку tracecov. Инструмент новый, такого в других фреймворках я не видел.
В чем суть? Там мы считаем не "покрытие кода", а намного более важную метрику: "покрытие тестами нашего АПИ". Ну то есть буквально:
• Какие операции были вызваны?
• С какими телами и параметрами?
• Какие ответы получены по статусам?
• Какие схемы возвращены?
• Работают ли примеры из доки?
Так как мы используем очень строгую схему - у нас такой подход хорошо работает.
Мы интегрировали поддержку tracecov в наш dmr_client, который используется для всех интеграционных тестов. И schemathesis, который мы используем для property-based тестирования OpenAPI спецификации - тоже поддерживает такое.
Один запуск schemathesis позволяет добиться примерно 85+% покрытия всего АПИ. Вау! То есть: тесты можно почти не писать с таким походом.
В pyproject.toml можно добавить:
# Tracecov:
"--tracecov-format=text,html,markdown",
"--tracecov-fail-under-operations=100",
"--tracecov-fail-under-examples=100",
# TODO: set value to 100
"--tracecov-fail-under-parameters=90",
"--tracecov-fail-under-keywords=90",
"--tracecov-fail-under-responses=50",
И тогда тесты будут падать при низком покрытии АПИ. Вот куда можно развиваться, если у вас - как у нас - уже 100% обычного покрытия.
Одной строкой
• Добавили поддержку attrs для моделей
• Добавили msgpack как протокол для АПИ, он значительно быстрее json
• Добавили JsonLines для стриминга событий
• Переработали несколько апишек, стало значительно удобнее. Спасибо первым пользователям за обратную связь!
Обсуждение: Воспользовались бы такой метрикой? И какое покрытие вы считаете оптимальным? И почему 100%?
P.S. Выпустил большую статью про django-modern-rest на Хабру: https://habr.com/ru/articles/1017036 Если есть плюсики - буду очень благодарен за помощь в продвижении!
| Поддержать | YouTube | GitHub | Чат |RESULT = yield from EXPR в CPython.
_i = iter(EXPR)
try:
_y = next(_i)
except StopIteration as _e:
_r = _e.value
else:
while 1:
try:
_s = yield _y
except GeneratorExit as _e:
try:
_m = _i.close
except AttributeError:
pass
else:
_m()
raise _e
except BaseException as _e:
_x = sys.exc_info()
try:
_m = _i.throw
except AttributeError:
raise _e
else:
try:
_y = _m(*_x)
except StopIteration as _e:
_r = _e.value
break
else:
try:
if _s is None:
_y = next(_i)
else:
_y = _i.send(_s)
except StopIteration as _e:
_r = _e.value
break
RESULT = _r
Источник: https://peps.python.org/pep-0380
Никогда не спрашивайте такое на собесах, будьте людьми 🌚
Что будет тут?
async def agenerator():
yield 1
return 2
async def main():
result = async yield from agenerator()
assert result == 2
Страшно. Очень страшно.
P.S. Два поста в один день, когда такое было?!async yield from. И у меня есть много разных мыслей.
Во-первых, оно реально иногда удобно. Во-вторых, реально консистентно визуально и синтаксически с синхронными генераторами:
async def agenerator():
yield 1
yield 2
async def main():
async yield from agenerator()
С другой стороны: https://pyfound.blogspot.com/2024/06/python-language-summit-2024-limiting-yield-in-async-generators.html
> Guido van Rossum lamented that this was "yet another demonstration that async generators were a bridge too far. Could we have a simpler PEP that proposes to deprecate and eventually remove from the language asynchronous generators, just because they're a pain and tend to spawn more complexity".
> Zac had no objections to a PEP deprecating async generators¹. Zac continued, "while static analysis is helpful in some cases, there are inevitably cases that it misses which kept biting us... until we banned all async generators in our codebase".
И вроде бы на примере выше оно выглядит нормально. Но давайте чуть глубже посмотрим.
Кстати, недавно наш коллега – Сергей Мирянов – добавил секцию "Async generators best practices" в доку asyncio.
Всем советую: https://docs.python.org/3.15/library/asyncio-dev.html#asynchronous-generators-best-practices
Если вы можете найти в данном коде 4 ошибки, то можете не читать доку. Остальным обязательно.
import asyncio
work_done = False
async def cursor():
try:
yield 1
finally:
assert work_done
async def rows():
global work_done
try:
yield 2
finally:
await asyncio.sleep(0.1) # immitate some async work
work_done = True
async def main():
async for c in cursor():
async for r in rows():
break
break
asyncio.run(main())
Какие проблемы там подсвечены?
1. Явно использование aclosing(agen) контекста для закрытия AsyncGenerator, иначе может пропасть стадия "уборки за собой", а сам генератор может остаться живым
2. Порядок очистки ресурсов в асинхронных генераторах может быть не таким, как вы думаете
3. Запуск асинхронных генераторов без event loop - плохая идея
4. Итерация асинхронного генератора из двух разных тасок = ошибка
Так вот! Стоит ли углубляться туда?
Обсуждение: что вы думаете про асинхронные генераторы и их развитие? Можете ли честно сказать, что понимаете, как они работают? Можете найти баги с asyncio.CanceledError и очисткой состояния без запуска кода? Я - нет.
| Поддержать | YouTube | GitHub | Чат |django-channels (фу, Артём, без негатива)
– Поддержка других форматов стриминга кроме SSE, например JsonL
– Поддержка cattrs и adaptix
– Скилы для LLM для автоматизации перехода с django-rest-framework и django-ninja
– Поддержка ty
Благодарности
Данный проект не стал бы возможен без:
– Александра и Алексея – соавторов проекта, они затащили гигантский объем работы
– Виктора, кто сделал нам офигительную интерактивную доку!
– А так же 51 других контрибьюторов, кто внес неоценимый вклад в проект
Большое спасибо всем за помощь, обратную связи и поддержку, без вас – ничего бы не вышло.
Лучшее сообщество! 🫶
Ну а я – делаю небольшой перерыв, отдыхаю и работаю дальше!
Обсуждение: какие фичи вы бы хотели увидеть в дальнейших релизах?
P.S. Если у вас есть подкаст / канал / тд, и вы хотите поговорить со мной про веб фреймворки на питоне – пишите в личку! Сделаем интересное :)
| Поддержать | YouTube | GitHub | Чат |
import uuid
import msgspec
from dmr import Body, Controller
from dmr.plugins.msgspec import MsgspecSerializer
class UserCreateModel(msgspec.Struct):
email: str
class UserModel(UserCreateModel):
uid: uuid.UUID
class UserController(
Controller[MsgspecSerializer],
Body[UserCreateModel],
):
def post(self) -> UserModel:
return UserModel(uid=uuid.uuid4(), email=self.parsed_body.email)
Фичи
– Главная фича, которая вообще подтолкнула меня к такому проекту: инфраструктура Джанги. Тут есть буквально все пакеты на все случаи жизни. Но не было нормального REST фреймворка. В комментах я регулярно наблюдал, как люди ненавидят Джангу, но почти всегда говорят про DRF. Да, он был ужасен – то теперь он на свалке истории!
– Все существующие плагины к родной Джанге должны работать
– Официальная поддержка Джанго в одном файле, да, Джанга может быть настолько простой
– Работаем с любыми моделями: pydantic, msgspec, TypedDict, dataclass, тд. Сериализация и валидация не прибиты гвоздями. А значит можно выбирать сериализатор под контроллер. Где-то msgspec + TypedDict для скорости. Где-то pydantic для более широких возможностей валидации. Можно писать свои
– Скорость. Мы довольно быстрые. Самый быстрый Python фреймворк для REST в Django. По скорости можно сравнивать с FastAPI, мы всего лишь на 30% медленнее. Но у нас и Джанга вообще-то. Скорость будет улучшаться, есть разные интересные идеи
– Типизация: типизировано всё! Но самое важное, типизацию не пихают вам в лицо. Нет огромных и сложных типов. Все просто, надежно и удобно. Поддерживаем mypy, pyright, pyrefly в самых строгих вариантах
– Поддержка async везде. От вьюх и моделей до SSE. Никаких sync_to_async внутри
– SSE! Без дополнительных костылей: просто работает (с валидацией сообщений и возможностью строить бизнесовые ADT поверх типов сообщений и крутейшей схемой)
– Семантика. Одна из ключевых фичей: мы очень сильно упоролись по генерации схемы. Добавил auth= в контроллер? В списке ответов появился 401 статус код автоматически. Возвращаешь ответ, заголовок, куку, которой нет в спеке? Во время дебага – случится ошибка валидации. На проде валидацию нужно отключать для скорости. Так мы гарантируем точность ответов и схемы. Не нравится схема? Все легко переопределить или вообще отключить
– Swagger, Scalar, Redoc из коробки, легко настраивать
– Работаем не только с json, поддерживаем content negotiation, можно писать свои парсеры и рендереры
– JWT и DjangoSessionAuth из коробки, есть возможность отзыва токенов и сессий
– Возможность писать заготовки контроллеров и полностью переиспользовать код. Писать плагины под dmr будет просто и удобно
– Загрузка и отдача файлов (но на питоне такое очень осторожно надо делать, лучше на Rust)
– Нет привязки к логике или DI (берите любой, например dishka). Мы просто парсим данные и возвращаем их. То есть: код не превратится в кашу из логики и фреймворка уже через 10 бизнес фичей
– Удобная обработка ошибок на многих уровнях
– Полная возможность для кастомизации. Можно даже поменять формат внутренних ошибок в рамках контроллера
– Удобные тесты: polyfactory, pytest, schemathesis (проходим все правила из коробки)
– Скилы для LLM для написания кода по OpenAPI спеке, llms-full.txt, Context7 для контекста
– Но никакого нейрослопа внутри!
@dataclass
class User:
username: str
age: int
def get_field(obj: Any, field_name: str) -> Any:
return getattr(obj, field_name)
user = User('example', 18)
username = get_field(user, 'username')
# ^ мы знаем, что тут `str`, но никак не можем такое выразить, кроме КУЧИ `@overload` для конкретного типа
# а для общего случая - вообще никак
Никак, обидно. Я даже 100 лет назад делал такую поделку: https://github.com/wemake-services/mypy-extras
Чтобы хоть как-то решать проблему выше.
Предложение
И вот Юрий Селиванов (автор asyncio и edge-db) предлагает добавить в питон специальные действия над типами.
Чтобы было как в TS, где есть условные и рекурсивные типы, готовые операторы как keyof и куча дополнительных типов в npm.
Вот что предлагают добавить:
<type> = ...
# Type booleans are all valid types too
| <type-bool>
# Conditional types
| <type> if <type-bool> else <type>
# Types with variadic arguments can have
# *[... for t in ...] arguments
| <ident>[<variadic-type-arg> +]
# Type member access
| <type>.<name>
| GenericCallable[<type>, lambda <args>: <type>]
А еще:
– Типовые операторы: IsAssignable, IsEquivalent, GetArg, FromUnion, тд
– Методы для интроспекции объектов в типах: Members, Attrs, GetMember, тд
– Создание типов внутри аннотаций: NewProtocol, NewTypedDict
Пример
Показать детали работы всего я, конечно, не смогу. Но смогу показать один пример из ПЕПа.
Понятная проблема: есть какая-то модель пользователя. При создании данной модели - мы указываем все поля, кроме primary_key. Но показывать мы будем наружу все поля, кроме password.
Сейчас мы делаем что-то типа
class UserBase(SQLModel):
name: str = Field(index=True)
age: int | None = Field(default=None, index=True)
class User(HeroBase, table=True):
id: int | None = Field(default=None, primary_key=True)
password: str = Field(hidden=True)
class UserPublic(UserBase):
id: int
class UserCreate(UserBase):
password: str
Но, мы можем создавать такие модели при помощи типов.
Полный код: https://github.com/vercel/python-typemap/blob/main/tests/test_fastapilike_2.py
# Extract the default type from an Init field.
# If it is a Field, then we try pulling out the "default" field,
# otherwise we return the type itself.
type GetDefault[Init] = (
GetFieldItem[Init, Literal["default"]]
if typing.IsAssignable[Init, Field]
else Init
)
# Create takes everything but the primary key and preserves defaults
type Create[T] = typing.NewProtocol[
*[
typing.Member[
p.name,
p.type,
p.quals,
GetDefault[p.init],
]
for p in typing.Iter[typing.Attrs[T]]
if not typing.IsAssignable[
Literal[True],
GetFieldItem[p.init, Literal["primary_key"]],
]
]
]
Данная страшная конструкция будет спрятана внутри SQLModel, а мы будем писать просто:
UserCreate = Create[User]
А внутри уже:
– Полная типизация всех полей
– Новая корректная модель, которая всегда актуальна
Круто?
Мое мнение: в детали данного предложения я пока не вникал, но в целом - направление правильное.
Обсуждение: а что вы думаете про такое развитие типизации в питоне?
P.S. Из телеги и ютюба не перекатываемся. Рекламы на канале и так почти не было, для меня - мало что меняется.
Если вы хотите поддерживать мою работу в опенсорсе и контент без рекламы скам-курсов и вечных прогревов, то всегда можно закинуть на бусти: https://boosty.to/sobolevn
| Поддержать | YouTube | GitHub | Чат |type[T]?
from typing import Any
def validate[T](typ: type[T], value: Any) -> None: ...
validate(int, 1)
Но, на самом деле у нас тут есть ошибка. typ будет ожидать любой объект класса type, а не любую аннотацию.
Например, int | str не является объектом класса type, но является валидной аннотацией. Так же как и: None, Literal[1], Self, T, тд.
Пример в pyright.
Данная фича реально нужна авторам библиотек, кто строит свою логику работы на типах.
Реальный пример: в django-modern-rest (нативная интеграция 🌚️️️️) мы создаем метаданные об ответе ResponseSpec(return_type=int | str, status_code=200) для такого кода пользователя:
from dmr import Controller
from dmr.plugins.msgspec import MsgspecSerializer
class UserController(Controller[MsgspecSerializer]):
async def post(self) -> int | str: ...
На данный момент ResponseSpec.return_type имеет аннотацию Any, как единственный рабочий вариант.
Но в идеале мы будем использовать TypeForm[Any] в ближайшее время.
Как и многие другие:
• pydantic
• msgspec
• dishka (использует аннотации для DI)
• Даже некоторые места stdlib, например: dataclasses.fields
Итоговый пример:
from typing_extensions import TypeForm, Any
def validate[T](typ: TypeForm[T], value: Any) -> T: ...
reveal_type(validate(int, 1)) # int
reveal_type(validate(int | str, 1)) # int | str
Текущее состояние
В typing_extensions код уже есть, поддержка в mypy будет в версии 1.20 (следующей).
В pyright поддержка уже есть полтора года как.
В Python3.15 будет нативно в typing.
Еще одна хорошая фича.
Обсуждение: какие варианты использования TypeForm?
| Поддержать | YouTube | GitHub | Чат |dict лежат в одном файле на 8к строк)
Зачем?
Главный вопрос: зачем питону вдруг через 35 лет понадобился иммутабельный словарь? Мотивации в ПЕПе явно не очень хватает. Но я докину:
1. frozendict можно будет шарить между разными интерпретаторами без какого-либо оверхеда
2. С иммутабельными объектами куда проще работать в режиме Free-Threading
3. Многие другие новые идеи вроде Виртуальных Потоков тоже хотели бы иметь аналог иммутабельного словаря
Ну а types.MappingProxyType(mapping) был только surface-immutable. Все равно можно было поменять оригинальный объект mapping.
И вот у нас появилась точная копия обычного dict, только иммутабельная:
Примеры
frozendict является collections.abc.Mapping и таким же Generic с двумя параметрами:
>>> frozendict.__mro__
(<class 'frozendict'>, <class 'object'>)
>>> obj = frozendict({'a': 1})
>>> frozendict[str, int]
frozendict[str, int]
Но не умеет ничего из collections.abc.MutableMapping:
>>> obj['a'] = 2
TypeError: 'frozendict' object does not support item assignment
>>> obj.update
AttributeError: 'frozendict' object has no attribute 'update'
Зато умеет в hash, если все ключи и значения умеют в hash:
>>> hash(obj)
6343282633043897990
>>> hash(frozendict({1: []}))
TypeError: unhashable type: 'list'
Как его менять? А вот так, создавая новые:
>>> obj = frozendict({'a': 1})
>>> id(obj)
4352339472
>>> obj |= {'b': 2} # <- тут мы создали новый frozendict
>>> obj
frozendict({'a': 1, 'b': 2})
>>> id(obj)
4352341680
Детали реализации
Чтобы вы понимали, насколько они похожи: frozendict просто переиспользует clinic макросы dict для определения своих методов (=использует те же методы):
static PyMethodDef frozendict_methods[] = {
DICT___CONTAINS___METHODDEF
{"__getitem__", dict_subscript, METH_O | METH_COEXIST, getitem__doc__},
DICT___SIZEOF___METHODDEF
DICT_GET_METHODDEF
DICT_KEYS_METHODDEF
DICT_ITEMS_METHODDEF
DICT_VALUES_METHODDEF
DICT_FROMKEYS_METHODDEF
DICT_COPY_METHODDEF
DICT___REVERSED___METHODDEF
{"__class_getitem__", Py_GenericAlias, METH_O|METH_CLASS, PyDoc_STR("See PEP 585")},
{NULL, NULL} /* sentinel */
};
Интересно, как работает hash: он полностью дублирует алгоритм хеша из frozenset.
В C-API тоже добавили функций для работы с новым словарем: PyFrozenDict_New, PyAnyDict_Check проверяет на dict, frozendict или их подтипы.
А еще половина stdlib поменяет константы с dict на frozendict.
Отличный ПЕП, простая реализация, крутая фича. Питон победа!
Обсуждение: как вы относитесь к иммутабельности в питоне и вообще?
| Поддержать | YouTube | GitHub | Чат |django_modern_rest, уже скоро.typing_extensions: https://github.com/python/typing_extensions/blob/a7610ef567132cac2b5319fa193c830b655364c6/src/typing_extensions.py#L358
Когда я критикую систему типов в питоне, говоря, что её не продумывали заранее, то disjoint_base - отличный пример моих слов.
В питоне можно наследоваться от нескольких классов (и вообще всего с методом __mro_entries__, да). Что иногда довольно удобно, если использовать без фанатизма. Но проблема в том, что не все наборы классов подходят для множественного наследования.
Например:
>>> class What(str, int): ...
Traceback (most recent call last):
TypeError: multiple bases have instance lay-out conflict
Почему такое происходит почитать можно тут. Но, нам важно узнать, что в CPython есть концепция "solid base", которая считается для всех созданных классов вот тут. Если очень кратко, то CPython должен уметь построить правильный memory-layout для всех классов потомков. Например, все потомки int должны быть вот так уложены в памяти:
typedef struct _PyLongValue {
uintptr_t lv_tag; /* Number of digits, sign and flags */
digit ob_digit[1];
} _PyLongValue;
struct _longobject {
PyObject_HEAD
_PyLongValue long_value;
};
Иначе - перестанет работать базовая логика int. А str устроены по-другому.
/* Object format for Unicode subclasses. */
typedef struct {
PyCompactUnicodeObject _base;
union {
void *any;
Py_UCS1 *latin1;
Py_UCS2 *ucs2;
Py_UCS4 *ucs4;
} data; /* Canonical, smallest-form Unicode buffer */
} PyUnicodeObject;
Сделать область памяти для потомка int и str сразу - невозможно. Потому и выкидывается ошибка.
Но, сделать так можно и со своими классами, не обязательно использовать C, достаточно конфликта в __slots__:
>>> class A:
... __slots__ = ('a',)
>>> class B:
... __slots__ = ('b',)
>>> class C(A, B): ...
Traceback (most recent call last):
TypeError: multiple bases have instance lay-out conflict
Подробности из ПЕПа про "solid bases".
Типизация
Теперь int, str и многие другие классы в typeshed помечены как @disjoint_base типы. Значит, что только один такой класс может быть в mro. Раньше такое костылили все тайпчекеры по-своему.
Как следствие, тайпчекеры теперь более четко смогут находить и другие проблемы. Например, в местах где мы создаем "временный" тип:
def g(x: int):
match x:
case str(): # unreachable
print("It's both!")
Раньше такой код проходил в некоторых тайпчекерах. Ведь они думали, что тип подкласс int и str может существовать. А теперь - будут знать, что такое невозможно на уровне определения и выкидывать правильную ошибку.
Отличный ПЕП, система типов стала чуть лучше.
Обсуждение: знали ли вы про solid и disjoint bases в питоне? Стреляли ли себе в ногу таким?
| Поддержать | YouTube | GitHub | Чат |cProfile (написан на C) был перемещен в profiling.tracing
- В 3.15 profile (написан на Python) стал deprecated, его уберут в 3.17
- Добавили новый profiling.sampling (кодовое имя `tachyons`), о нем и поговорим
- А еще добавили встроенные TUI (на скриншоте) и GUI
Ключевые фичи:
- Almost Zero-overhead: добиваемся такого, за счет того, что переодически получаем готовые стектрейсы работающих процессов, не нужно добавлять инструментацию функций как в cProfile
- Не нужно менять существующий код
- Разные режимы работы: attach позволяет подключиться к работающему Python процессу, а run позволяет запустить код для профилировки
- Разные виды замеров: --mode wall, --mode cpu, --mode gil, --mode exception
- Поддержка тредов
- Поддержка asyncio с флагом --async-aware, можно смотреть даже suspended tasks с --async-mode=all
- Разные форматы вывода информации: --flamegraph, --pstats, --heatmap, --gecko
- --live вместе с TUI для отображения информации в реальном времени (как в top условном)
- Поддержка профилирования до уровня опкодов виртуальной машины, включая специализации
- Гибкая конфигурация буквально всего
Примеры запуска:
- python -m profiling.sampling run --flamegraph -o profile.html script.py - запускаем скрипты и генерим флеймграф
- python -m profiling.sampling attach --live $YOUR_PID - профилируем работающий процесс и получаем данные в реальном времени
На чем основана работа технически?
- PEP-768 с remote-debugging предоставляет техническую возможность быстро и легко получать семплы из виртуальной машины
- Для asyncio используется новый АПИ для async-aware call-graphs
Личное мнение: очень крутая фича, пока у меня вопросы по TUI / GUI. Не очень понимаю, зачем их затащили в stdlib. Зарепортил пару багов. На маке требуется запускать профилировщик с sudo -E. Остальное - нравится!
Кирилл вот тоже заценил.
Обсуждение: Что вы думаете о данной фиче? Как вы сейчас профилируете ваши приложения в разработке и в проде?
P.S. Последний пост в текущем году. Всех с наступающим! 🎄
| Поддержать | YouTube | GitHub | Чат |git clone с нуля, но и самое главное – на скорость всех наших сборок (если мы не используем fetch-depth: 1 или аналог, а использовать их надо).
Решение: использовать git-lfs!
Я пригласил замечательного Олега Чирухина @tg_1red2black, чтобы обсудить:
- Как работает git-lfs на базовом уровне?
- Как мигрировать на него с базового сетапа?
- Как он устроен внутри? Поднимаем https://github.com/git-lfs/lfs-test-server и детально смотрим, что там внутри происходит
Ну и конечно чуть-чуть глянули исходники, они, кстати, на #go 🌚️️️️
Обсуждение: как вы храните большие файлы в рабочих проектах? Насколько большие файлы вы храните?
| Поддержать | YouTube | GitHub | Чат |Raw: для выделения памяти для общих задач, например под сишные буферы или IO. Может работать без PyThreadState
- Mem: для выделения памяти для общих задач, но уже с PyThreadState, например под Python буферы, подходит для мелких объектов
- Object: для выделения памяти под конкретные мелкие объекты
Разработчики C-extensions должны понимать, когда какой использовать и под какие задачи.
К счастью, разработчикам на питоне - такое нужно только для любопытства.
А вот таблица, какие реальные аллокаторы используют те или иные C-API функции в разных режимах:
PyMem_RawMalloc -> malloc
PyMem_Malloc -> pymalloc
PyObject_Malloc -> pymalloc
Она правда немного устарела и не отражает Free-Threading сборки, которые требуют mimalloc 🌚
Кто первый успеет сделать PR с исправлением - тот молодец!
О mimalloc мы как-нибудь отдельно поговорим, там нужно рассказывать сильно глубже, в том числе про GC и PyGC_Head.
Зачем питону свой аллокатор?
В CPython есть (был? для free-threading он не используется и не будет) свой аллокатор: pymalloc, основная задача которого – работа с маленькими Python объектами.
Про него полностью тоже нужно писать большой отдельный пост.
Что вообще важно в аллокаторе?
- Стратегия выделения памяти под новый запрос
- Работа с округлениями размера памяти и выравнивание
- Дефрагментация памяти
- Стратегия очистки памяти
struct arena_object {
uintptr_t address;
pymem_block* pool_address;
uint nfreepools;
uint ntotalpools;
struct pool_header* freepools;
struct arena_object* nextarena;
struct arena_object* prevarena;
};
Но кратко про pymalloc можно сказать следующее:
- Он создает арены по 1MB
- Внутри арены разделены на пулы по 16KB
- Внутри пулы поделены на блоки фиксированного размера
Зачем? Чтобы не аллоцировать часто маленькие кусочки памяти. Что дорого.
Можно ли управлять аллокаторами?
Да! Есть опции для сборки: --without-mimalloc, --without-pymalloc
И даже переменная окружения PYTHONMALLOC, которая позволяет указать, какой аллокатор использовать для всех случаев. Зачем? Прежде всего для дебага. Но можно потестить, вдруг будет давать буст по скорости или потреблению памяти в ваших вариантах использования.
Обсуждение: какой ваш любимый аллокатор? И почему jemalloc?
| Поддержать | YouTube | GitHub | Чат |
#[unsafe(no_mangle)]
pub unsafe extern "C" fn b64encode(
_module: *mut PyObject,
args: *mut *mut PyObject,
nargs: Py_ssize_t,
) -> *mut PyObject {
if nargs != 1 {
unsafe {
PyErr_SetString(
PyExc_TypeError,
c"b64encode() takes exactly one argument".as_ptr(),
);
}
return ptr::null_mut();
}
let source = unsafe { *args };
let buffer = match unsafe { BorrowedBuffer::from_object(source) } {
Ok(buf) => buf,
Err(_) => return ptr::null_mut(),
};
let view_len = buffer.len();
if view_len < 0 {
unsafe {
PyErr_SetString(
PyExc_TypeError,
c"b64encode() argument has negative length".as_ptr(),
);
}
return ptr::null_mut();
}
let input_len = view_len as usize;
let input = unsafe { slice::from_raw_parts(buffer.as_ptr(), input_len) };
let Some(output_len) = encoded_output_len(input_len) else {
unsafe {
PyErr_NoMemory();
}
return ptr::null_mut();
};
if output_len > isize::MAX as usize {
unsafe {
PyErr_NoMemory();
}
return ptr::null_mut();
}
let result = unsafe {
PyBytes_FromStringAndSize(ptr::null(), output_len as Py_ssize_t)
};
if result.is_null() {
return ptr::null_mut();
}
let dest_ptr = unsafe { PyBytes_AsString(result) };
if dest_ptr.is_null() {
unsafe {
Py_DecRef(result);
}
return ptr::null_mut();
}
let dest = unsafe { slice::from_raw_parts_mut(dest_ptr.cast::<u8>(), output_len) };
let written = encode_into(input, dest);
debug_assert_eq!(written, output_len);
result
}
Все подробности будут в @cpython_notes
Обсуждение: что думаете?
| Поддержать | YouTube | GitHub | Чат |
"custom-ui-style.stylesheet": {
"#status\\.problems": {
"visibility": "hidden !important",
"display": "none !important",
}
}
- И создавать локальные плагины на #javascript для кастомизации поведения редактора
function updateColumnPosition() {
const positionLabel = document.querySelector(
'#status\\.editor\\.selection .statusbar-item-label',
)
if (!positionLabel || !positionLabel.textContent) {
// It might not exist for some reason ¯\_(ツ)_/¯
return
}
const currentLine = document.querySelector(
'.line-numbers.active-line-number',
)
// Now we would have the column position instead of the active line number:
const colNumber = positionLabel.textContent.match(/Col (\d+)/)
currentLine.textContent = colNumber[1]
}
Впереди еще пара взрывающих мозг видосов, будем и дальше превращать vscode в Черный Квадрат Малевича.
В следующих видео:
- Уберем оставшиеся части UI
- Сделаем свою тему, нативная интеграция самой минималистичной темной темы от меня (есть для vsocde и nvim): https://github.com/pustota-theme/pustota
- Поговорим про git
- Научимся пользоваться дебагером, как будто мы трухацкеры
Обсуждение: То что vscode - браузер - плюс или минус лично для вас? И почему?
| Поддержать | YouTube | GitHub | Чат |django-modern-rest, без нескольких будущих оптимизаций (привет cython и rust!), голая джанга начинает выглядеть не так уж и медленнее FastAPI: бенчи.
Во-вторых, Django на длительный срок был в плену DRF. Который морально устарел еще в середине десятых.
Потом появилась django-ninja, которая хоть и намного лучше, но все равно не решает главную задачу: интеграция с существующими тулами. Конечно, если затащить FastAPI дизайн в чужой мир, то ничего не будет нормально работать 🌚️️
Вот такие проблемы мы решаем.
Одной строкой:
- Супер строгая OpenAPI схема и валидация при разработке, что мы ее соблюдаем
- Полная типизация всего
- Нормальный способ создания АПИ, без god-functions a-la FastAPI
- async без sync_to_async
- Быстро: msgspec для парсинга json (самый быстрый способ в питоне), практически 0 работы в рантайме (все делаем при импорте), оптимизации роутера (быстрее дефолтного в 51 раз)
- Пидантик, msgspec, или любой другой способ выражать модели. Хоть adaptix
- Полная поддержка всего существующего в Django. От декораторов до миддлварь (с полной валидацией и OpenAPI схемой!)
- OpenAPI схема из коробки
- Удобное тестирование с polyfactory
- property-based тесты вашего АПИ одной командой благодаря schemathesis
Что еще будет:
- RSGI (да, на #rust скорее всего) под granian, в теории можем побить FastAPI после такого
- SSE с автоматической OpenAPI спекой и умной валидацией схемы
- JWT или любая другая аутентификация
- Какие-то части (скорее всего валидацию) мы компильнем cython, чтобы было еще быстрее
Modern? Modern!
Осталось дождаться. Релиз скоро!
И еще скоро будут несколько новых видео.
Мы регулярно постим задачки в @opensource_findings_python, так что можно нам помочь.
А если кто хочет мне закинуть на развитие опенсорса (за написание 32500 строк кода за 2 недели), то мой бусти открыт: https://boosty.to/sobolevn
Ну и звезды можно ставить, конечно же. Там посмотрите, какие люди нас уже советуют.
Большое спасибо Александру, Алексею, Роману, Максиму и всем остальным за неоценимую помощь. Пацаны, без вас никак! Лучшее сообщество!
Обсуждение: а чего бы вам хотелось в modern rest фреймворке? Чего сильно не хватает сейчас?
lazy import json
lazy from json import dumps
Как будет работать?
Импорты не будут подгружаться до момента первого обращения к объекту.
import sys
lazy import json
print('json' in sys.modules) # Модуля еще нет
# Загрузка начинается вот тут:
result = json.dumps({"hello": "world"})
print('json' in sys.modules) # Теперь он загружен
Зачем?
- Быстрее загружать модули
- Позволит убрать много импортов из функций / методов
- Позволит убрать if TYPE_CHECKING: import some_module, было множество предложений по добавлению import type конструкции, пример: https://discuss.python.org/t/type-only-imports/96755
- Позволит решать циклические импорты
- Убирает необходимость в LazyLoader и страшных вещей вроде https://scientific-python.org/specs/spec-0001
Детали реализации
Планируется добавить:
- Функцию __lazy_import__ (аналог __import__ для импорта )
- Специальный атрибут на уровне модуля __lazy_modules__, где будут храниться ленивые импорты текущего модуля
- types.LazyImportType как тип нового "ленивого" модуля (аналог `types.ModuleType`)
- sys.lazy_modules для списка ленивых модулей глобально
- sys.set_lazy_imports_filter для глобальных настроек импортов, что? 🤯
- Байткод IMPORT_NAME получит флаг, является ли импорт ленивым
- Ошибки в именах модулей тоже будут ленивыми https://peps.python.org/pep-0810/#reification
Из забавного: import * не будет доступен в lazy режиме.
# SyntaxError: lazy from ... import * is not allowed
lazy from json import *
Реализация: https://github.com/LazyImportsCabal/cpython/tree/lazy
Обсуждение: как вам?
Бонус: меня тут в комментах спрашивают, куда я пропал. Я пропал в опенсорсе, пилю django-modern-rest. Простую и удобную библиотеку для REST API в Django. Пока на стадии pre-alpha, но уже скоро будет первый релиз. Можно уже ставить ⭐, чтобы потом всем говорить, что первыми узнали про новую модную штуку!
https://github.com/wemake-services/django-modern-rest
| Поддержать | YouTube | GitHub | Чат |
from dataclasses import dataclass
from litestar import Litestar, post
@dataclass
class User:
name: str
@post(path="/")
async def index(data: User) -> User:
return data
app = Litestar(route_handlers=[index])
И он классный:
- Быстрый
- Работает с Pydantic, msgspec и даже базовые dataclasses в качестве моделей
- Предоставляет базовые примитивы для работы с логикой: DTOs, Repository, Service (часть из них в advanced_sqlalchemy, очень удобно для крудов)
- Умеет из коробки в аутентификацию, метрики, DI, CORS, CSRF, кеширование, логирование, мониторинг, вебсокеты, htmx, сессии, загрузку файлов, разные ORMки и много всего еще
- Можно делать не только API, но и нормальные шаблончики
- Он с нами уже несколько лет, он работает
- А еще его делает большая команда, а не один человек
Кароче, просто лучший фреймворк (кроме Django, конечно).
Но! Сегодня речь пройдет про минусы. Которые скоро пофиксят в v3 (или которые все еще будут с нами)
Минусы
1. Самое главное: DI на именах. Да. Вот так:
class UserController(Controller):
path = "/user"
dependencies = {"user": Provide(retrieve_db_user)}
@patch(path="/{user_id:uuid}") # <- тут тоже можно указывать `dependencies`
async def get_user(self, user: User) -> User: ...
Что очень плохо. Тип user, который мы указываем в dependencies и в параметрах может не совпасть. Есть некоторая валидация для такого в рантайме, но все равно очень рисково.
Планы? Я предложил использовать dishka в качестве дефолтного DI, а старый задеприкейтить.
Высказать свое обоснованное мнение можно тут (или просто лайкнуть пост): https://github.com/orgs/litestar-org/discussions/4377
Хочу верить, что мы сможем поправить данную проблему в v3, ваши голоса – могут помочь :)
2. DI в middleware. А точнее отсутствие DI в middleware. Пока что приходится прокидывать зависимости через app.state, что очень криво. Данная проблема признана ошибкой дизайна и будет исправлено в v3, ура!
3. Своя схема для OpenAPI. Litestar переписал дефолтную схему pydantic, что позволило использовать более строгую схему, но переодически новые типы отваливаются. Например, pydantic.Json не обработан, имеет кривую схему. Сейчас мы обсуждаем стоит ли откатиться до дефолтной схемы. Стоит ли?
4. PydanticPlugin игнорирует ConfigDict, который объявлен в самой моделе, в v3 будет пофикшено. Сейчас параметры вроде by_alias и round_trip надо передавать в плагин. Что свело меня с ума, искал ошибку несколько часов
5. CLI местами разваливается, очень много багов и плохая читаемость
6. Очень сырая документация. Очень плохая навигация и структура, очень мало примеров, мало людей ее читало, мало людей находило проблемы, мало людей присылало PRы. Тут нужна ваша помощь очень сильно!
Итог
Отличный фреймворк, у которого есть некоторое количество детских проблем и ошибок проектирования. Но что отличает? Про них говорят, их исправляют.
Полный роадмап для версии v3: https://github.com/litestar-org/litestar/issues/4009
Обсуждение: Как вам Litestar? Какие минусы я забыл? Каких фичей не хватает? Что показалось сложным? Буду рад обратной связи :)