`slots=True` ломает ваши датаклассы!
Когда прям с заголовка набросил, то дальше уже всегда проще.
Давайте посмотрим, какую пользу и какой вред приносит использование
Во-первых, что делает
1. Валидирует, что
2. Генерирует дескрипторы для всех имен в
3. Если слот
Из-за чего больше нельзя будет назначать произвольные атрибуты:
Но, достаточно добавить
4.
Разница в скорости доступа не очень большая, но есть:
Против доступа со
Так что там с датаклассами?
Штука в том, что создать слоты в существующем классе – нельзя физически. Слишком много всего написано на C.
Можно только пересоздать класс еще раз 😱
https://github.com/python/cpython/blob/91ff700de28f3415cbe44f58ce84a2670b8c9f15/Lib/dataclasses.py#L1224-L1276
Что порождает много проблем. Например:
- Нельзя использовать
Потому что
- Нельзя использовать `__init_subclass__` в
Так что - будьте осторожны!
Когда прям с заголовка набросил, то дальше уже всегда проще.
Давайте посмотрим, какую пользу и какой вред приносит использование
@dataclass(slots=True) или @attr.define(slots=True). В целом - различий не так много.Во-первых, что делает
__slots__ = ('a',) внутри класса?
class My:
__slots__ = ('a',)
1. Валидирует, что
__slots__ корректны2. Генерирует дескрипторы для всех имен в
__slots__, см https://github.com/python/cpython/blob/91ff700de28f3415cbe44f58ce84a2670b8c9f15/Objects/descrobject.c#L793-L796
>>> class My:
... __slots__ = ('a',)
...
>>> My.a, type(My.a)
(<member 'a' of 'My' objects>, <class 'member_descriptor'>)
3. Если слот
__dict__ не проставлен, то меняет базовую функцию доступа к и установки аттрибутов
/* Special case some slots */
if (type->tp_dictoffset != 0 || ctx->nslot > 0) {
PyTypeObject *base = ctx->base;
if (base->tp_getattr == NULL && base->tp_getattro == NULL) {
type->tp_getattro = PyObject_GenericGetAttr;
}
if (base->tp_setattr == NULL && base->tp_setattro == NULL) {
type->tp_setattro = PyObject_GenericSetAttr;
}
}
Из-за чего больше нельзя будет назначать произвольные атрибуты:
>>> class My:
... __slots__ = ('a',)
...
>>> m = My()
>>> m.custom = 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'My' object has no attribute 'custom'
Но, достаточно добавить
'__dict__' внутрь __slots__, чтобы вернуть данное поведение: __slots__ = ('a', '__dict__'):
>>> class My:
... __slots__ = ('a', '__dict__')
...
>>> m = My()
>>> m.custom = 0
4.
__slots__ ускоряет доступ к атрибутам и уменьшает размер объектов.
>>> import sys
>>> class A:
... __slots__ = ('a',)
... def __init__(self, a):
... self.a = a
>>> class B:
... def __init__(self, a):
... self.a = a
>>> sys.getsizeof(A(1))
40
>>> sys.getsizeof(B(1))
56
Разница в скорости доступа не очень большая, но есть:
» pyperf timeit -s '
class A:
def __init__(self, a):
self.a = a
a = A(1)' 'a.a'
.....................
Mean +- std dev: 13.9 ns +- 0.1 ns
Против доступа со
__slots__:
» pyperf timeit -s '
. class A:
. __slots__ = ("a",)
. def __init__(self, a):
. self.a = a
.
. a = A(1)' 'a.a'
.....................
Mean +- std dev: 13.3 ns +- 0.1 ns
Так что там с датаклассами?
Штука в том, что создать слоты в существующем классе – нельзя физически. Слишком много всего написано на C.
Можно только пересоздать класс еще раз 😱
https://github.com/python/cpython/blob/91ff700de28f3415cbe44f58ce84a2670b8c9f15/Lib/dataclasses.py#L1224-L1276
cls = type(cls)(cls.__name__, cls.__bases__, cls_dict)
Что порождает много проблем. Например:
- Нельзя использовать
super() без параметров в методах внутри тела класса:
>>> @dataclass(slots=True)
... class My:
... def __str__(self) -> str:
... return super().__str__()
...
>>> str(My())
Traceback (most recent call last):
File "<python-input-2>", line 1, in <module>
str(My())
~~~^^^^^^
File "<python-input-1>", line 4, in __str__
return super().__str__()
^^^^^^^^^^^^^^^
TypeError: super(type, obj): obj (instance of My) is not an instance or subtype of type (My).
Потому что
__str__.closure не обновляет cell объекты на другой класс при пересоздании. Есть PR, но все сложно: https://github.com/python/cpython/pull/111538- Нельзя использовать `__init_subclass__` в
родителях класса со slots, где ожидаются параметры. Тут только документацией можно помочь: https://github.com/python/cpython/pull/123342Так что - будьте осторожны!
🔥35👍13🤔7❤5🤯3🤡3
Материалы с PythoNN 30 августа 2024
Мы проводим питон митапы в Нижнем Новгороде раз в квартал уже несколько лет.
30 августа к нам приезжало два замечательных гостя:
- Александр Гончаров – "Это вообще не просто!" https://www.youtube.com/watch?v=0EFHpmEgXak
- Андрей Пронин – "Как увеличить зарплату в два раза за год?" https://www.youtube.com/watch?v=IfLT_ssxOhU
Внутри есть ссылки и на презентации к докладам, и на личные страницы гостей.
Друзья, спасибо большое за интересные доклады. Спасибо гостям за отличную атмосферу и интересные вопросы.
Если хотите побывать в НН и заодно сделать доклад – пишите, буду рад помочь с подготовкой!
Мы проводим питон митапы в Нижнем Новгороде раз в квартал уже несколько лет.
30 августа к нам приезжало два замечательных гостя:
- Александр Гончаров – "Это вообще не просто!" https://www.youtube.com/watch?v=0EFHpmEgXak
- Андрей Пронин – "Как увеличить зарплату в два раза за год?" https://www.youtube.com/watch?v=IfLT_ssxOhU
Внутри есть ссылки и на презентации к докладам, и на личные страницы гостей.
Друзья, спасибо большое за интересные доклады. Спасибо гостям за отличную атмосферу и интересные вопросы.
Если хотите побывать в НН и заодно сделать доклад – пишите, буду рад помочь с подготовкой!
🔥34👏5😱1
Находки в опенсорсе
`slots=True` ломает ваши датаклассы! Когда прям с заголовка набросил, то дальше уже всегда проще. Давайте посмотрим, какую пользу и какой вред приносит использование @dataclass(slots=True) или @attr.define(slots=True). В целом - различий не так много. Во…
Продолжаем ломать dataclass'ы со `__slots__`!
Некоторое время назад прилетел баг: https://github.com/python/cpython/issues/118033
Причина? Причина нам пока не очень понятна. Давайте разбираться. Баг состоит из нескольких частей.
Weakref
Что вообще такое
Следовательно: когда мы создаем объект со слотами, нам необходимо, чтобы слот
PEP 695
В Python3.12, как мы все знаем, добавили новый синтаксис для
Чтобы реализовать данную возможность, часть кода была переписана с Python на C. Например, появились такие файлы как:
- https://github.com/python/cpython/blob/main/Objects/typevarobject.c
- https://github.com/python/cpython/blob/main/Include/internal/pycore_typevarobject.h
- https://github.com/python/cpython/blob/main/Modules/_typingmodule.c
Значит, что в Python3.12
А значит, что у них поменялись внутренности устройства.
И теперь классам с
Следовательно, чтобы правильно работали слоты – нужно проверить родителя.
Offsets
В сишных типах обычно не объявляют
Что за слоты такие?
Если вы смотрите "Лучший курс по питону", то вы их уже много раз видели (а если не смотрите, то почему?!). Слоты =
-
-
- Полный список: https://docs.python.org/3/c-api/typeobj.html#tp-slots
Есть несколько специальных слотов, которые нас сегодня интересуют:
- Слот
- Слот
Посмотрим:
Смотрим на разницу:
Фатальное изменение
И последняя часть. К нам пришел вот такой PR: https://github.com/python/cpython/commit/a22d05f04c074dbb4f71e7837f54c0bb693db75d
Некоторое время назад прилетел баг: https://github.com/python/cpython/issues/118033
from dataclasses import dataclass
@dataclass(slots=True, weakref_slot=True)
class Token[T]:
ctx: T
print(hasattr(Token, '__weakref__'))
# 3.12.2: True
# 3.12.3: False
Причина? Причина нам пока не очень понятна. Давайте разбираться. Баг состоит из нескольких частей.
Weakref
Что вообще такое
__weakref__? Конечно же, оно связано с модулем weakref для создания слабых ссылок, которые не увеличивают ob_refcnt объекта. Внутри __weakref__ будет хранится объект ссылки. Смотрим:
>>> class My: ...
...
>>> import weakref
>>> m = My()
>>> w = weakref.ref(m)
>>> m.__weakref__
<weakref at 0x103c77d20; to 'My' at 0x103be9920>
>>> m.__weakref__ is w
True
Следовательно: когда мы создаем объект со слотами, нам необходимо, чтобы слот
__weakref__ существовал. Иначе – будет ошибка:
>>> class WithSlots:
... __slots__ = () # no '__weakref__'
...
>>> weakref.ref(WithSlots())
TypeError: cannot create weak reference to 'WithSlots' object
PEP 695
В Python3.12, как мы все знаем, добавили новый синтаксис для
TypeVar, ParamSpec, TypeVarTuple и Generic классов, функций и алиасов.Чтобы реализовать данную возможность, часть кода была переписана с Python на C. Например, появились такие файлы как:
- https://github.com/python/cpython/blob/main/Objects/typevarobject.c
- https://github.com/python/cpython/blob/main/Include/internal/pycore_typevarobject.h
- https://github.com/python/cpython/blob/main/Modules/_typingmodule.c
Значит, что в Python3.12
Generic стал С типом.А значит, что у них поменялись внутренности устройства.
И теперь классам с
[] автоматически назначается родитель: _typing.Generic
>>> class Example[T]: ...
...
>>> Example.__bases__
(<class 'typing.Generic'>,)
Следовательно, чтобы правильно работали слоты – нужно проверить родителя.
Offsets
В сишных типах обычно не объявляют
__slots__, потому что используют другие - сишные - слоты.Что за слоты такие?
Если вы смотрите "Лучший курс по питону", то вы их уже много раз видели (а если не смотрите, то почему?!). Слоты =
tp_* места для вставки разных обработчиков под разные случаи жизни. Например:-
tp_new для __new__-
tp_richcompare для сравнений >, <, тд- Полный список: https://docs.python.org/3/c-api/typeobj.html#tp-slots
Есть несколько специальных слотов, которые нас сегодня интересуют:
- Слот
tp_dictoffset или макро Py_TPFLAGS_MANAGED_DICT, который указывает, что у объекта есть __dict__- Слот
tp_weakrefoffset или макро Py_TPFLAGS_MANAGED_WEAKREF, который указывает, что у объекта есть __weakref__Посмотрим:
PyType_Spec typevar_spec = {
.name = "typing.TypeVar",
.flags = ... | Py_TPFLAGS_MANAGED_DICT | Py_TPFLAGS_MANAGED_WEAKREF,
};
// vs
PyType_Spec generic_spec = {
.name = "typing.Generic",
// No `__dictoffset__` and no `__weakrefoffset__`:
.flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
};
Смотрим на разницу:
>>> from _typing import TypeVar, Generic
>>> TypeVar.__dictoffset__, TypeVar.__weakrefoffset__
(-1, -32)
>>> Generic.__dictoffset__, Generic.__weakrefoffset__
(0, 0)
>>> weakref.ref(TypeVar('A'))
<weakref at 0x103c77c40; dead>
>>> weakref.ref(Generic())
TypeError: cannot create weak reference to 'typing.Generic' object
Фатальное изменение
И последняя часть. К нам пришел вот такой PR: https://github.com/python/cpython/commit/a22d05f04c074dbb4f71e7837f54c0bb693db75d
def _get_slots(cls):
match cls.__dict__.get('__slots__'):
# A class which does not define __slots__ at all is equivalent
# to a class defining __slots__ = ('__dict__', '__weakref__')
case None:
yield from ('__dict__', '__weakref__')
# ...
👍13❤3🔥3
Находки в опенсорсе
`slots=True` ломает ваши датаклассы! Когда прям с заголовка набросил, то дальше уже всегда проще. Давайте посмотрим, какую пользу и какой вред приносит использование @dataclass(slots=True) или @attr.define(slots=True). В целом - различий не так много. Во…
Который предполагал, что если
Я пофиксил вот так: https://github.com/python/cpython/commit/fa9b9cb11379806843ae03b1e4ad4ccd95a63c02
Теперь мы правильно учитываем наличие C слотов в том числе. И правильно определяем, что у базового класса
Дело закрыто! Ну как вам погружение? :)
__slots__ явно не объявлены у типа, то по-умолчанию стоят __dict__ и __weakref__. Что правда для Python типов, но нельзя забывать про C типы, как я показывал выше.Я пофиксил вот так: https://github.com/python/cpython/commit/fa9b9cb11379806843ae03b1e4ad4ccd95a63c02
def _get_slots(cls):
match cls.__dict__.get('__slots__'):
# `__dictoffset__` and `__weakrefoffset__` can tell us whether
# the base type has dict/weakref slots, in a way that works correctly
# for both Python classes and C extension types. Extension types
# don't use `__slots__` for slot creation
case None:
slots = []
if getattr(cls, '__weakrefoffset__', -1) != 0:
slots.append('__weakref__')
if getattr(cls, '__dictrefoffset__', -1) != 0:
slots.append('__dict__')
yield from slots
Теперь мы правильно учитываем наличие C слотов в том числе. И правильно определяем, что у базового класса
Generic нет слота __weakref__. И нам нужно его добавить в наш новый датакласс. Дело закрыто! Ну как вам погружение? :)
1🤯28🔥11👍6
Находки в опенсорсе
Который предполагал, что если __slots__ явно не объявлены у типа, то по-умолчанию стоят __dict__ и __weakref__. Что правда для Python типов, но нельзя забывать про C типы, как я показывал выше. Я пофиксил вот так: https://github.com/python/cpython/commit…
Внимательный читатель (спасибо, Вася) заметил, что у меня ОПЕЧАТКА В ФИКСЕ. там написано
https://github.com/python/cpython/issues/123935
Данный кейс не был протестирован. И сейчас я уже отправляют ЕЩЕ ОДИН PR, теперь уже точно финальный.
Вот и польза от поста 🌚️️
__dictrefoffset__, а не __dictoffset__, как должно быть. https://github.com/python/cpython/issues/123935
Данный кейс не был протестирован. И сейчас я уже отправляют ЕЩЕ ОДИН PR, теперь уже точно финальный.
Вот и польза от поста 🌚️️
GitHub
Incorrect slot check: typo in `__dictoffest__` · Issue #123935 · python/cpython
Bug report I made a typo that made it into the final code: cpython/Lib/dataclasses.py Lines 1211 to 1212 in 00ffdf2 if getattr(cls, '__dictrefoffset__', -1) != 0: slots.append('__dict__...
🔥56👍12❤2
Лучший курс по Python 9: Переменные
https://www.youtube.com/watch?v=crSzQKfevZU
Я хотел сделать видео про переменные, которое бы рассказывало: а как на самом деле происходит создание и поиск имени? Все рассказывают про переменные, как про какие "коробки" для значений. А не они не коробки! Потому, в видео про переменные я рассказываю:
- Что никаких переменных в Python – нет 🌚
- Про
- Про генерацию байткода: покрываем все стадии через
- Про замыкания с
- Ну и про рантайм конечно же! Как работает, например
Бонус! Я показывал видео Грише Петрову до публикации. Он дал ценную обратную связь: я не упомянул, почему иногда модификация
-
На самом деле
будет делать
И изменения видны не будут.
- А вот тут я пропустил шаг: так а почему на уровне модуля / REPL оно работает?
Потому что в REPL / модуле
Где
- Далее, на слайде я показываю, но повторю данную мысль еще раз:
Не надо так! Не модифицируйте скоупы, просто потому что можно!
Надеюсь, что было интересно.
Поддержать такой контент можно тут:
- https://boosty.to/sobolevn
- https://github.com/sponsors/wemake-services
#lkpp
https://www.youtube.com/watch?v=crSzQKfevZU
Я хотел сделать видео про переменные, которое бы рассказывало: а как на самом деле происходит создание и поиск имени? Все рассказывают про переменные, как про какие "коробки" для значений. А не они не коробки! Потому, в видео про переменные я рассказываю:
- Что никаких переменных в Python – нет 🌚
- Про
frame.f_locals и frame.f_globals- Про генерацию байткода: покрываем все стадии через
symtable.c / compile.c / codegen.c- Про замыкания с
.__closure__ и MAKE_CELL- Ну и про рантайм конечно же! Как работает, например
globals() и locals() на самом деле
/*[clinic input]
globals as builtin_globals
Return the dictionary containing the current scope's global variables.
NOTE: Updates to this dictionary *will* affect name lookups in the current
global scope and vice-versa.
[clinic start generated code]*/
static PyObject *
builtin_globals_impl(PyObject *module)
/*[clinic end generated code: output=e5dd1527067b94d2 input=9327576f92bb48ba]*/
{
PyObject *d;
d = PyEval_GetGlobals();
return Py_XNewRef(d);
}
Бонус! Я показывал видео Грише Петрову до публикации. Он дал ценную обратную связь: я не упомянул, почему иногда модификация
locals().update() работает, а иногда нет. Исправляюсь!-
locals(), как показано в видео, обычно возвращает новый dict, потому что использует прокси внутри C. Внутри функции модификация locals() работать не будет. И вот почему, код:
// PyObject * _PyEval_GetFrameLocals(void)
if (PyFrameLocalsProxy_Check(locals)) {
PyObject* ret = PyDict_New();
if (ret == NULL) {
Py_DECREF(locals);
return NULL;
}
if (PyDict_Update(ret, locals) < 0) {
Py_DECREF(ret);
Py_DECREF(locals);
return NULL;
}
Py_DECREF(locals);
return ret;
}
assert(PyMapping_Check(locals));
return locals;
На самом деле
def some():
locals().update({'a': 1})
print(a)
будет делать
.update на *другом* dict объекте, который мы создали из PyFrameLocalsProxy, а не сам прокси.И изменения видны не будут.
- А вот тут я пропустил шаг: так а почему на уровне модуля / REPL оно работает?
Потому что в REPL / модуле
frame.f_locals is frame.f_globals. Вот код:
static int
PyRun_InteractiveOneObjectEx(FILE *fp, PyObject *filename,
PyCompilerFlags *flags)
{
PyArena *arena = _PyArena_New();
if (arena == NULL) {
return -1;
}
mod_ty mod;
PyObject *interactive_src;
int parse_res = pyrun_one_parse_ast(fp, filename, flags, arena, &mod, &interactive_src);
PyObject *main_module = PyImport_AddModuleRef("__main__");
PyObject *main_dict = PyModule_GetDict(main_module); // borrowed ref
PyObject *res = run_mod(mod, filename, main_dict, main_dict, flags, arena, interactive_src, 1);
// ...
Где
static PyObject *
run_mod(mod_ty mod, PyObject *filename, PyObject *globals, PyObject *locals,
PyCompilerFlags *flags, PyArena *arena, PyObject* interactive_src,
int generate_new_source)
{
- Далее, на слайде я показываю, но повторю данную мысль еще раз:
NOTE: Whether or not updates to this dictionary will affect name lookups in
the local scope and vice-versa is *implementation dependent* and not
covered by any backwards compatibility guarantees.
Не надо так! Не модифицируйте скоупы, просто потому что можно!
Надеюсь, что было интересно.
Поддержать такой контент можно тут:
- https://boosty.to/sobolevn
- https://github.com/sponsors/wemake-services
#lkpp
YouTube
Лучший курс по Python 9: Переменные
Лучший курс по питону: 9
Или "обзор исходников CPython с CPython core разработчиком".
Тема: переменные
- Что такое "переменная" в Python?
- Есть ли в Python "переменные"?
- Чем "переменная" отличается от "имени"?
- Что такое `locals()` и `globals()` в Python?…
Или "обзор исходников CPython с CPython core разработчиком".
Тема: переменные
- Что такое "переменная" в Python?
- Есть ли в Python "переменные"?
- Чем "переменная" отличается от "имени"?
- Что такое `locals()` и `globals()` в Python?…
3🔥57❤19👍10🤔1🤯1
Атрибут `__class__` в Python можно переписывать! 😱
Пример:
Но как?
Обратимся к исходникам в
Тут можно увидеть сразу несколько важных моментов:
1. Данное действие совсем-совсем не thread-safe, чтобы его совершить приходится останавливать все остальные треды в режиме noGIL
2. У каждого объекта в Python есть поле
Важно, оба класса, должны быть совместимы, иначе будут ошибки:
- Оба класса должны быть мутабельными
- Оба класса должны иметь совместимый binary layout (см функцию
-
Но зачем?
Данный хак используется в большом количестве мест в исходниках.
- Например, такое есть прямо в доках
И его можно менять:
Внутри есть
-
- В
- В
Пример:
>>> class Cat:
... def meow(self):
... print('meow')
>>> class Dog:
... def bark(self):
... print('woof!')
>>> c = Cat()
>>> c.__class__ = Dog # превращаем котика в собачку!
>>> isinstance(c, Dog)
True
>>> c.bark()
woof!
Но как?
Обратимся к исходникам в
typeobject.c:
static PyGetSetDef object_getsets[] = {
{"__class__", object_get_class, object_set_class,
PyDoc_STR("the object's class")},
{0}
};
static PyObject *
object_get_class(PyObject *self, void *closure)
{
return Py_NewRef(Py_TYPE(self));
}
static int
object_set_class(PyObject *self, PyObject *value, void *closure)
{
// ...
PyTypeObject *newto = (PyTypeObject *)value;
#ifdef Py_GIL_DISABLED
PyInterpreterState *interp = _PyInterpreterState_GET();
_PyEval_StopTheWorld(interp);
#endif
PyTypeObject *oldto = Py_TYPE(self);
// Calls:
// ob->ob_type = newto;
int res = object_set_class_world_stopped(self, newto);
#ifdef Py_GIL_DISABLED
_PyEval_StartTheWorld(interp);
#endif
}
Тут можно увидеть сразу несколько важных моментов:
1. Данное действие совсем-совсем не thread-safe, чтобы его совершить приходится останавливать все остальные треды в режиме noGIL
2. У каждого объекта в Python есть поле
ob_type, где хранится его тип. А значит, тип можно менятьВажно, оба класса, должны быть совместимы, иначе будут ошибки:
- Оба класса должны быть мутабельными
- Оба класса должны иметь совместимый binary layout (см функцию
compatible_for_assignment в `typeobject.c`)-
__slots__ должны быть одинаковымиНо зачем?
Данный хак используется в большом количестве мест в исходниках.
- Например, такое есть прямо в доках
Mock: https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.__class__
>>> mock = Mock()
>>> mock.__class__ = dict
>>> isinstance(mock, dict)
True
И его можно менять:
@property
def __class__(self):
if self._spec_class is None:
return type(self)
return self._spec_class
Внутри есть
__setattr__, который вместо .__class__ = X будет менять ._spec_class = X. А свойство будет отражать изменение.-
LazyLoader в importlib делает такое: https://github.com/python/cpython/blob/46f5cbca4c37c57f718d3de0d7f7ddfc44298535/Lib/importlib/util.py#L273
def exec_module(self, module):
"""Make the module load lazily."""
# Threading is only needed for lazy loading, and importlib.util can
# be pulled in at interpreter startup, so defer until needed.
import threading
module.__spec__.loader = self.loader
module.__loader__ = self.loader
loader_state = {}
loader_state['__dict__'] = module.__dict__.copy()
loader_state['__class__'] = module.__class__
loader_state['lock'] = threading.RLock()
loader_state['is_loading'] = False
module.__spec__.loader_state = loader_state
module.__class__ = _LazyModule # <---
- В
annotationlib / typing такое используется, что превращать строковое представление аннотаций в ForwardRef: https://github.com/python/cpython/blob/46f5cbca4c37c57f718d3de0d7f7ddfc44298535/Lib/annotationlib.py#L580-L581
class _Stringifier:
# Must match the slots on ForwardRef, so we can turn an instance of one into an
# instance of the other in place.
__slots__ = _SLOTS
# ...
for obj in globals.stringifiers:
assert isinstance(obj, _Stringifier)
obj.__class__ = ForwardRef
- В
threading используется, чтоб _DummyThread мог притворяться MainThread: https://github.com/python/cpython/blob/46f5cbca4c37c57f718d3de0d7f7ddfc44298535/Lib/threading.py#L1419
def _after_fork(self, new_ident=None):
if new_ident is not None:
self.__class__ = _MainThread
self._name = 'MainThread'
self._daemonic = False
Thread._after_fork(self, new_ident=new_ident)
Python documentation
unittest.mock — mock object library
Source code: Lib/unittest/mock.py unittest.mock is a library for testing in Python. It allows you to replace parts of your system under test with mock objects and make assertions about how they hav...
1🤯29🔥9👍3🤔3
Практическое применение?
Все мои посты всегда объединяет одно – понятная практическая ценность! 🌚️️️️️️
Не будем же отступать от традиции и здесь.
Зачем такое может понадобиться в реальном проекте? Я вижу две основные задачи:
- Написание кастомных моков / стабов в тестах. Замена
- Можно хачить модули!
Таким образом вы можете менять поведение модулей для каких-то супер специфичных штук помимо
Давайте обсудим: разрешили бы вы такое в своей кодовой базе? И почему нет?
Все мои посты всегда объединяет одно – понятная практическая ценность! 🌚️️️️️️
Не будем же отступать от традиции и здесь.
Зачем такое может понадобиться в реальном проекте? Я вижу две основные задачи:
- Написание кастомных моков / стабов в тестах. Замена
.__class__ в таком случае имеет понятную ценность, что объект делает вид, что он совсем другой объект. Ну и понимание, как работает стандартный Mock() и Mock(spec=X) - Можно хачить модули!
import sys
import types
class VerboseModule(types.ModuleType):
def __setattr__(self, attr, value):
print(f'setting {attr} to {value}')
super().__setattr__(attr, value)
sys.modules[__name__].__class__ = VerboseModule
Таким образом вы можете менять поведение модулей для каких-то супер специфичных штук помимо
__dir__ и __getattr__ на уровне модуляДавайте обсудим: разрешили бы вы такое в своей кодовой базе? И почему нет?
Python documentation
unittest.mock — mock object library
Source code: Lib/unittest/mock.py unittest.mock is a library for testing in Python. It allows you to replace parts of your system under test with mock objects and make assertions about how they hav...
😱23👍11🤔3🤯2❤1😁1
Вышел 3.13-rc3
Новости одной строкой:
- Последний релиз перед 3.13.0
- Официальная дата релиза 3.13 перенесена на 7 октября
- В релизе был ревертнут новый инкрементальный GC (https://github.com/python/cpython/pull/124770), потому что он вызывал регрессии по перформансу. Например:
- Такое уже случалось, первая версия инкрементального GC сделала CPython в примерно 20 раз медленнее (https://github.com/python/cpython/issues/117108)
- Как теперь будет работать
- Ждем новый сборщик мусора в 3.14
Новости одной строкой:
- Последний релиз перед 3.13.0
- Официальная дата релиза 3.13 перенесена на 7 октября
- В релизе был ревертнут новый инкрементальный GC (https://github.com/python/cpython/pull/124770), потому что он вызывал регрессии по перформансу. Например:
sphinx-build стал на 48% медленней (https://github.com/python/cpython/issues/124567)- Такое уже случалось, первая версия инкрементального GC сделала CPython в примерно 20 раз медленнее (https://github.com/python/cpython/issues/117108)
- Как теперь будет работать
nogil со старым сборщиком – я пока не понимаю 🤔️️️️️️- Ждем новый сборщик мусора в 3.14
🔥31👍18😱5
Лучший курс по Python 10: ==
44 минуты про сравнения, что может быть лучше?
https://www.youtube.com/watch?v=o-Ng_73kdik
В видео будет про:
- Сравнения в Python2 и усиление типизации в Python3
- Оптимизация байткода в Tier1:
- Разницу байткода и перформанса между
-
- Работу с
- Дефолтную реализацию
И даже за 44 минуты я не успел рассказать все! Делюсь дополнительными материалами здесь.
1. В ролике был вопрос: почему перед опкодом
Следовательно, если у нас не будет
То, мы для превращения объекта в
Документация: https://github.com/python/cpython/blob/main/Tools/cases_generator/interpreter_definition.md
2. Я не показал
Исходник: https://github.com/python/cpython/blob/656b7a3c83c79f99beac950b59c47575562ea729/Objects/longobject.c#L3548-L3567
Код кажется очевидным. Теперь полный цикл сравнений пройден :)
Вопрос для обсуждения:
Поддержать такой контент можно тут:
- https://boosty.to/sobolevn
- https://github.com/sponsors/wemake-services
44 минуты про сравнения, что может быть лучше?
https://www.youtube.com/watch?v=o-Ng_73kdik
В видео будет про:
- Сравнения в Python2 и усиление типизации в Python3
- Оптимизация байткода в Tier1:
COMPARE_OP превращается в COMPARE_OP_{INT,STR,FLOAT}- Разницу байткода и перформанса между
a == b == c и a == b and b == c-
PyObject_RichCompare C-API- Работу с
NotImplemented- Дефолтную реализацию
object.__eq__, object.__lt__ и другихИ даже за 44 минуты я не успел рассказать все! Делюсь дополнительными материалами здесь.
1. В ролике был вопрос: почему перед опкодом
TO_BOOL идет дополнительный COPY? Ответ будет такой. Как выглядит опредление опкода TO_BOOL? op(_TO_BOOL, (value -- res)). Теперь, давайте разбираться, что такое (value -- res). bytecodes.c написан на специальном DSL, который упрощает определение работы с байткодом для разных уровней оптимизаторов, а так же делает работу со стеком виртуальной машины похожей на "вызов функций". (value -- res) значит: возьми со стека value, положи на стек res
op(_TO_BOOL, (value -- res)) {
int err = PyObject_IsTrue(PyStackRef_AsPyObjectBorrow(value));
DECREF_INPUTS();
ERROR_IF(err < 0, error);
res = err ? PyStackRef_True : PyStackRef_False;
}
Следовательно, если у нас не будет
COPY:
pure inst(COPY, (bottom, unused[oparg-1] -- bottom, unused[oparg-1], top)) {
assert(oparg > 0);
top = PyStackRef_DUP(bottom);
}
То, мы для превращения объекта в
bool в TO_BOOL возьмем со стека value, превратим его в bool, сложим на стек результат, его проверит POP_JUMP_IF_FALSE. А самого значения уже не останется. COPY позволяет сохранить само значение объекта в стеке, для дальнейшей работы с ним.Документация: https://github.com/python/cpython/blob/main/Tools/cases_generator/interpreter_definition.md
2. Я не показал
long_compare. Исправляюсь:
static Py_ssize_t
long_compare(PyLongObject *a, PyLongObject *b)
{
if (_PyLong_BothAreCompact(a, b)) {
return _PyLong_CompactValue(a) - _PyLong_CompactValue(b);
}
Py_ssize_t sign = _PyLong_SignedDigitCount(a) - _PyLong_SignedDigitCount(b);
if (sign == 0) {
Py_ssize_t i = _PyLong_DigitCount(a);
sdigit diff = 0;
while (--i >= 0) {
diff = (sdigit) a->long_value.ob_digit[i] - (sdigit) b->long_value.ob_digit[i];
if (diff) {
break;
}
}
sign = _PyLong_IsNegative(a) ? -diff : diff;
}
return sign;
}
Исходник: https://github.com/python/cpython/blob/656b7a3c83c79f99beac950b59c47575562ea729/Objects/longobject.c#L3548-L3567
Код кажется очевидным. Теперь полный цикл сравнений пройден :)
Вопрос для обсуждения:
a == b == c или a == b and b == c? Какой стиль вы выбираете у себя?Поддержать такой контент можно тут:
- https://boosty.to/sobolevn
- https://github.com/sponsors/wemake-services
YouTube
Лучший курс по Python 10: ==
Лучший курс по питону: 10
Или "обзор исходников CPython с CPython core разработчиком".
Тема: сравнения
- Разница между == и is в Python
- Как работают сравнения в Python? ==, != и другие
- Зачем нужен NotImplemented в Python и в сравнениях?
- Стандартные…
Или "обзор исходников CPython с CPython core разработчиком".
Тема: сравнения
- Разница между == и is в Python
- Как работают сравнения в Python? ==, != и другие
- Зачем нужен NotImplemented в Python и в сравнениях?
- Стандартные…
1🔥38❤17👍7😱5
Находки в опенсорсе
Лучший курс по Python 10: == 44 минуты про сравнения, что может быть лучше? https://www.youtube.com/watch?v=o-Ng_73kdik В видео будет про: - Сравнения в Python2 и усиление типизации в Python3 - Оптимизация байткода в Tier1: COMPARE_OP превращается в COMPARE_OP_{INT…
И еще один прикол про сравнения забыл!
Речь, конечно же, идет про многолетнего кор-разработчика и релиз-менеджера: https://github.com/warsaw
Кстати, в новом REPL в Python3.13 данную фичу сломали:
Я открыл багрепорт: https://github.com/python/cpython/issues/124960
>>> from __future__ import barry_as_FLUFL
>>> 1 <> 2
True
Речь, конечно же, идет про многолетнего кор-разработчика и релиз-менеджера: https://github.com/warsaw
Кстати, в новом REPL в Python3.13 данную фичу сломали:
>>> from __future__ import barry_as_FLUFL
>>> 1 <> 2
File "<python-input-1>", line 1
1 <> 2
^^
SyntaxError: invalid syntax
Я открыл багрепорт: https://github.com/python/cpython/issues/124960
😁38🤔7❤3👍1🤬1💩1
Находки в опенсорсе
И еще один прикол про сравнения забыл! >>> from __future__ import barry_as_FLUFL >>> 1 <> 2 True Речь, конечно же, идет про многолетнего кор-разработчика и релиз-менеджера: https://github.com/warsaw Кстати, в новом REPL в Python3.13 данную фичу сломали:…
Все смешнее и смешнее 😂️️️️️️
😁52🔥4👍1🤔1
Находки в опенсорсе
Все смешнее и смешнее 😂️️️️️️
Кажется, что я случайно создал самый смешной багрепорт месяца :)
(ответ релиз-менеджера 3.13)
Кстати, в питон3.14 хотят добавить другой прикол: https://github.com/python/cpython/issues/119535
(ответ релиз-менеджера 3.13)
Кстати, в питон3.14 хотят добавить другой прикол: https://github.com/python/cpython/issues/119535
😁43🔥8❤2👍2
type alias'ы в пятницу вечером
История одного PR: https://github.com/python/cpython/pull/124795
Как вы знаете, в PEP695 (https://peps.python.org/pep-0695/) были добавлены новые тайпалиасы, которые работают поверх нового синтаксиса:
У него довольно много граничений. Например:
- Нельзя использовать дубликаты в именах параметров:
- Нельзя использовать литералы вместо имен:
- С Python3.13 нельзя использовать типовые параметры без дефолта после параметра с дефолтами
Сколько на самом деле тайпалиасов?
Однако, все не так просто. Ведь на самом деле синтаксис
Сравните с "ручным" способом (они почти идентичны, кроме ленивости вычисления `list[T]`):
Так вот 🌚️️️️
У меня еще не все. Есть еще один
В
Вот он: https://github.com/python/typing_extensions/blob/17d3a37635bad3902c4e913a48d969cbebfb08c3/src/typing_extensions.py#L3503
Проблемы в реализации
Пользователь нашел проблему, что при ручном создании
Теперь проверки есть, например вот так мы проверяем, что типовые параметры без дефолта не идут после параметров с дефолтами:
Практическая ценность
Кстати, такие тайпалиасы уже работают! Смотрите на
https://mypy-play.net/?mypy=latest&python=3.12&enable-incomplete-feature=NewGenericSyntax&flags=strict&gist=7b33597fb3f0cb723ac816efc2e2caef
Как вы используете type alias'ы в своем коде?
Какой вид предпочитаете:
История одного PR: https://github.com/python/cpython/pull/124795
Как вы знаете, в PEP695 (https://peps.python.org/pep-0695/) были добавлены новые тайпалиасы, которые работают поверх нового синтаксиса:
type ResultE[T] = Result[T, Exception]У него довольно много граничений. Например:
- Нельзя использовать дубликаты в именах параметров:
>>> type A[T, T] = ...
SyntaxError: duplicate type parameter 'T'
- Нельзя использовать литералы вместо имен:
>>> type A[1] = ...
SyntaxError: invalid syntax
- С Python3.13 нельзя использовать типовые параметры без дефолта после параметра с дефолтами
>>> type A[T=int, S] = ...
SyntaxError: non-default type parameter 'S' follows default type parameter
Сколько на самом деле тайпалиасов?
Однако, все не так просто. Ведь на самом деле синтаксис
type просто создает обычный рантайм объект типа typing.TypeAliasType. И его можно создать руками.
>>> type A[T] = list[T]
>>> type(A)
<class 'typing.TypeAliasType'>
>>> A.__type_params__
(T,)
>>> A.__value__
list[T]
Сравните с "ручным" способом (они почти идентичны, кроме ленивости вычисления `list[T]`):
>>> from typing import TypeAliasType, TypeVar
>>> T = TypeVar('T', infer_variance=True)
>>> A = TypeAliasType('A', list[T], type_params=(T,))
>>> type(A)
<class 'typing.TypeAliasType'>
>>> A.__type_params__
(T,)
>>> A.__value__
list[T]
Так вот 🌚️️️️
У меня еще не все. Есть еще один
TypeAliasType 😱В
typing_extensions для портирования поддержки библиотеками, кто инспектирует аннотации на 3.11 и ниже.Вот он: https://github.com/python/typing_extensions/blob/17d3a37635bad3902c4e913a48d969cbebfb08c3/src/typing_extensions.py#L3503
Проблемы в реализации
Пользователь нашел проблему, что при ручном создании
TypeAliasType не было никаких проверок. Почему? потому что все проверки были не в самом коде объекта, а на уровне парсера / компилятора. Руками туда можно было отправиль что угодно! И в C версию, и в Python версию.Теперь проверки есть, например вот так мы проверяем, что типовые параметры без дефолта не идут после параметров с дефолтами:
for (Py_ssize_t index = 0; index < length; index++) {
PyObject *type_param = PyTuple_GET_ITEM(type_params, index);
PyObject *dflt = get_type_param_default(ts, type_param);
if (dflt == NULL) {
*err = 1;
return NULL;
}
if (dflt == &_Py_NoDefaultStruct) {
if (default_seen) {
*err = 1;
PyErr_Format(PyExc_TypeError,
"non-default type parameter '%R' "
"follows default type parameter",
type_param);
return NULL;
}
} else {
default_seen = 1;
Py_DECREF(dflt);
}
}
Практическая ценность
Кстати, такие тайпалиасы уже работают! Смотрите на
--enable-incomplete-feature=NewGenericSyntax в mypy:
type A[T] = list[T]
def get_first[T](arg: A[T]) -> T:
return arg[0]
reveal_type(get_first([1, 2, 3]))
https://mypy-play.net/?mypy=latest&python=3.12&enable-incomplete-feature=NewGenericSyntax&flags=strict&gist=7b33597fb3f0cb723ac816efc2e2caef
Как вы используете type alias'ы в своем коде?
Какой вид предпочитаете:
TypeAlias, TypeAliasType, ключевое слово type?🤯28👍9😱6❤2
Forwarded from Никита Соболев
Большая сходка любителей настолок, питона и пива в Москве!
Где? Ресторан Paulaner на Полянке: https://yandex.ru/maps/org/paulaner/44880575916/?ll=37.620383%2C55.734745&z=17.97
Когда? Четверг 24 октября с 18:30 и до закрытия
Что в планах?
- Игра в https://github.com/sobolevn/ship-it-boardgame 0.0.19й версии
- Разговоры про программирование
Ждем всех :)
Где? Ресторан Paulaner на Полянке: https://yandex.ru/maps/org/paulaner/44880575916/?ll=37.620383%2C55.734745&z=17.97
Когда? Четверг 24 октября с 18:30 и до закрытия
Что в планах?
- Игра в https://github.com/sobolevn/ship-it-boardgame 0.0.19й версии
- Разговоры про программирование
Ждем всех :)
❤24🔥10👍3💩1
Техническое объявление
Я научился делать открытые чаты для канала 😅
Теперь можно вступать в @opensource_findings_chat и общаться на темы: Python, опенсорса, программирования и всего такого :)
Не забывайте о правилах: https://gist.github.com/sobolevn/d9a598a23e6bb89e51ada71033e9103f
В связи с последними событиями, я продублировал и закрытый чат из Дискорда в ТГ. Всех, кто подписан на https://boosty.to/sobolevn должно было пригласить автоматически. Если будут проблемы - пишите в чате, решим.
А в рамках ЛКПП 11 уже началось голосование за новую тему выпуска для желающих: https://boosty.to/sobolevn/posts/127ef142-1864-48e1-b410-fe49409c3192
Я научился делать открытые чаты для канала 😅
Теперь можно вступать в @opensource_findings_chat и общаться на темы: Python, опенсорса, программирования и всего такого :)
Не забывайте о правилах: https://gist.github.com/sobolevn/d9a598a23e6bb89e51ada71033e9103f
В связи с последними событиями, я продублировал и закрытый чат из Дискорда в ТГ. Всех, кто подписан на https://boosty.to/sobolevn должно было пригласить автоматически. Если будут проблемы - пишите в чате, решим.
А в рамках ЛКПП 11 уже началось голосование за новую тему выпуска для желающих: https://boosty.to/sobolevn/posts/127ef142-1864-48e1-b410-fe49409c3192
Gist
@opensource_findings: правила
@opensource_findings: правила. GitHub Gist: instantly share code, notes, and snippets.
🔥21👍5❤2👎1
Как ruff убил isort и поломал все мои проекты
Я пользовался
Который включал:
Где:
-
-
-
-
Все было хорошо, все работало годами. И тут появляется ruff. Большинство меинтейнеров проектов, которые ruff "заменил" – выгорают от таких поворотов. И перестают заниматься своими проектами.
В целом - и норм, потому что оно уже работало. До одного очень странного случая.
Буквально один из последних коммитов в isort - сломал мой профиль. Пришел человек, кто неправильно понял суть
Его PR без уточнений с моей стороны приняли. Релизнули новую версию
И у меня на всех проектах начал отваливаться линтер. Говорит: неправильно ты импорты оформляешь. Я очень удивился.
Какое-то время у меня ушло на дебаг, потому что случай странный. В итоге я нашел баг, сделал свой PR: https://github.com/PyCQA/isort/pull/2241
И тут авторы окончательно выгорели. Больше уже никто ничего не мерджил.
Я писал письма им в личку, пинговал коллег по PyCQA, заходил к ним в дискорд. Тишина.
Какие у меня есть варианты?
- Везде явно ставить
- Ставить прошлую версию
Ну и в ruff нет
Статический анализ – ад!
Я пользовался
isort сколько себя помню. Буквально с первых релизов, когда весь isort еще был написан в одном файле на много тысяч строк. Пользовался настолько активно, что у меня даже был свой --profile=wemake https://pycqa.github.io/isort/docs/configuration/profiles.html#wemakeКоторый включал:
[isort]
# profile = wemake
# =
multi_line_output = 3
include_trailing_comma = true
use_parentheses = true
line_length = 80
Где:
-
multi_line_output указывает, как разбивать на новые строки длинные импорты. Демо тут: https://pycqa.github.io/isort/docs/configuration/multi_line_output_modes.html-
include_trailing_comma добавляет финальные запятые для уменьшения diff при добавлении новых имен в импорт-
use_parentheses для использования () вместо \ - опять же для уменьшения diff-
line_length - максимальный размер строки, кстати он ничего не имеет общего с размером ваших мониторов. потому что длина строки - метрика сложности кода. код на 160 символов - в два раза сложнее. подробности тут: https://sobolevn.me/2019/10/complexity-waterfallВсе было хорошо, все работало годами. И тут появляется ruff. Большинство меинтейнеров проектов, которые ruff "заменил" – выгорают от таких поворотов. И перестают заниматься своими проектами.
В целом - и норм, потому что оно уже работало. До одного очень странного случая.
Буквально один из последних коммитов в isort - сломал мой профиль. Пришел человек, кто неправильно понял суть
line_length и поправил значение в --profile=wemake с 80 на 79 https://github.com/PyCQA/isort/pull/2183Его PR без уточнений с моей стороны приняли. Релизнули новую версию
isort. И у меня на всех проектах начал отваливаться линтер. Говорит: неправильно ты импорты оформляешь. Я очень удивился.
Какое-то время у меня ушло на дебаг, потому что случай странный. В итоге я нашел баг, сделал свой PR: https://github.com/PyCQA/isort/pull/2241
И тут авторы окончательно выгорели. Больше уже никто ничего не мерджил.
Я писал письма им в личку, пинговал коллег по PyCQA, заходил к ним в дискорд. Тишина.
Какие у меня есть варианты?
- Везде явно ставить
line_length = 80 в дополнение к --profile=wemake, что все еще ломает опыт всем пользователям https://github.com/wemake-services/wemake-python-styleguide- Ставить прошлую версию
isort, что тоже стремный хакНу и в ruff нет
--profile https://docs.astral.sh/ruff/settings/#lintisortСтатический анализ – ад!
😢46❤27🤯14🤬5😁4👍3👎1🔥1🤔1
Нерегулярная воскресная рубрика про интересный опенсорс
Если у вас есть интересные опенсорсные проекты, про которые вы хотите рассказать, то пишите в чат.
С вас пост. С меня редактура и размещение. Давайте помогать друг другу!
А сегодняшний пост будет про очень прикольную библиотеку https://github.com/airtai/faststream от ее автора.
FastStream
Это современный фреймворк для разработки асинхронных сервисов поверх брокеров сообщений. Он взлетел за счет очень простого, интуитивного API:
Но за простотой кроется достаточно интересное внутреннее устройство. Основная трудность, с которой борется FastStream (и почему у инструмента нет аналогов) - двухэтапная инициализация объектов. Это значит, что все вложенные объекты и данные, необходимые для функционирования "запчастей" не известны на момент их создания через
Как, например, в случае с мидлварями и декомпозицией приложения на отдельные router'ы
На момент регистрации ни
Все объекты должны создаваться готовыми к использования настолько, насколько возможно, но также поддерживать операции переноса между различными контейнерами (роутеры / брокеры).
Более того, мидлвари и другие объекты, передаваемые в
Как мы видим, для чтения сообщений необходим объект Consumer'а (который и держит
При запуске брокера, мы должны рекурсивно пройтись по всему дереву вложенных объектов и проинициализаровать его повторно реальными объектами, необходимыми для функционирования компонентов. Все это развестистое дерево двухэтапной инициализации с необходимостью сохранения валидности всех ссылок на уже созданные объекты приводит к довольно сложной, но интересной внутренней структуре проекта.
И это только небольшая часть сложностей, с которой вынужден бороться фреймворк! А там еще есть:
- in-memory тестирование
- собственный DI вдохновленный FastAPI
- сериализация на интроспекции типов
- поддержка разных бекендов: Kafka, RabbitMQ, Redis, NATS
- свой CLI
- много всякого-разного!
Если вы ищете интересный проект для участия в Open Source - FastStream сейчас нуждается в контрибуторах: ревью PR'ов, участие в обсуждениях, большие и маленькие фичи, правки в документацию - мы будем рады любому участию!
* Telegram группа проекта
* Доклад от создателя фреймворка с PiterPy
Если у вас есть интересные опенсорсные проекты, про которые вы хотите рассказать, то пишите в чат.
С вас пост. С меня редактура и размещение. Давайте помогать друг другу!
А сегодняшний пост будет про очень прикольную библиотеку https://github.com/airtai/faststream от ее автора.
FastStream
Это современный фреймворк для разработки асинхронных сервисов поверх брокеров сообщений. Он взлетел за счет очень простого, интуитивного API:
from faststream import FastStream
from faststream.rabbit import RabbitBroker
broker = RabbitBroker()
app = FastStream(broker)
@broker.subscriber("in-queue")
@broker.publisher("out-queue")
async def handle_msg(user: str) -> str:
return f"User: {user} registered"
Но за простотой кроется достаточно интересное внутреннее устройство. Основная трудность, с которой борется FastStream (и почему у инструмента нет аналогов) - двухэтапная инициализация объектов. Это значит, что все вложенные объекты и данные, необходимые для функционирования "запчастей" не известны на момент их создания через
__init__ и должны быть доставлены позже.Как, например, в случае с мидлварями и декомпозицией приложения на отдельные router'ы
from faststream.nats import NatsBroker, NatsRouter
from faststream.nats.prometheus import NatsPrometheusMiddleware
router = NatsRouter()
publisher = router.publisher("out")
@router.subscriber("in")
async def handler(msg):
await publisher.publish("in")
broker = NatsBroker(middlewares=[NatsPrometheusMiddleware()])
broker.include_router(router)
На момент регистрации ни
publisher, ни subscriber ничего не знают о своих будущих мидлварях. Они создаются позже, в брокере. Что значит: на момент включения router'а в брокер мы должны передать подобные зависимости в роутер. FastAPI, например, решает эту проблему путем создания новых эндпоинтов как копий из экземпляра router'a. Однако, тут данный подход не сработает - тот же publisher используется внутри кода обработчика. Пересоздавать объекты мы не можем - старая ссылка должна быть валидна.Все объекты должны создаваться готовыми к использования настолько, насколько возможно, но также поддерживать операции переноса между различными контейнерами (роутеры / брокеры).
Более того, мидлвари и другие объекты, передаваемые в
Broker.__init__ - самая безобидная часть айсберга. Большая часть объектов требует наличия реального объекта connection к брокеру (который появляется только после асинхронного await broker.start() ). Взглянем на небольшой кусочек кода aiokafka (используется внутри FastStream):
consumer = AIOKafkaConsumer(...)
await consumer.start()
async for msg in consumer:
...
Как мы видим, для чтения сообщений необходим объект Consumer'а (который и держит
connection ). Соответственно, нам нобходимо доставить этот объект до subscriber'ов FastStream уже после запуска приложения.При запуске брокера, мы должны рекурсивно пройтись по всему дереву вложенных объектов и проинициализаровать его повторно реальными объектами, необходимыми для функционирования компонентов. Все это развестистое дерево двухэтапной инициализации с необходимостью сохранения валидности всех ссылок на уже созданные объекты приводит к довольно сложной, но интересной внутренней структуре проекта.
И это только небольшая часть сложностей, с которой вынужден бороться фреймворк! А там еще есть:
- in-memory тестирование
- собственный DI вдохновленный FastAPI
- сериализация на интроспекции типов
- поддержка разных бекендов: Kafka, RabbitMQ, Redis, NATS
- свой CLI
- много всякого-разного!
Если вы ищете интересный проект для участия в Open Source - FastStream сейчас нуждается в контрибуторах: ревью PR'ов, участие в обсуждениях, большие и маленькие фичи, правки в документацию - мы будем рады любому участию!
* Telegram группа проекта
* Доклад от создателя фреймворка с PiterPy
1🔥73👍21❤11🕊2🤡2
`LOAD_CONST` разделили на три опкода в 3.14
https://github.com/python/cpython/pull/125972
В Python 3.14 распилили один из самых популярных опкодов:
Теперь
-
-
-
А еще и
И вот демо байткода:
Зачем нужен LOAD_SMALL_INT?
https://github.com/python/cpython/issues/101291
Если вы внимательно смотрели мой видос про int, то вы помните, как выглядят инты внутри питона:
Большие и сложные объекты. Но, для очень частых маленьких чисел, такое переусложнение замедляет работу. Мы можем просто представлять числа в рамках одного машинного слова и складывать их сразу в oparg, без необходимости заргужать их из
В Python2, кстати, работало быстрее, потому что там был честный
Обсуждение
Задумываетесь ли вы про подобные микро-оптимизации, когда пишите код?
https://github.com/python/cpython/pull/125972
В Python 3.14 распилили один из самых популярных опкодов:
LOAD_CONST. Он, как можно понять из названия, он загружал константы из frame->co_consts:
// 3.13:
pure inst(LOAD_CONST, (-- value)) {
value = GETITEM(FRAME_CO_CONSTS, oparg);
Py_INCREF(value);
}
>>> def func():
... return 1
>>> func.__code__.co_consts
(None, 1)
Теперь
LOAD_CONST разделен на:-
LOAD_SMALL_INT для интов в range(256)-
LOAD_CONST_IMMORTAL для загрузки бесмертных объектов (на 1 Py_INCREF меньше, см PyStackRef_FromPyObjectNew vs `PyStackRef_FromPyObjectImmortal`)-
LOAD_CONST для оставшихсяА еще и
RETURN_CONST удалили под шумок. И вот демо байткода:
>>> import dis
>>> def func():
... x = 1
... y = ...
... z = 'привет, мир'
>>> dis.dis(func, adaptive=True)
2 LOAD_SMALL_INT 1
STORE_FAST 0 (x)
3 LOAD_CONST 1 (Ellipsis)
STORE_FAST 1 (y)
4 LOAD_CONST 2 ('привет, мир')
STORE_FAST 2 (z)
LOAD_CONST 0 (None)
RETURN_VALUE
>>> # Create caches for tier1 adaptive interpreter to work:
>>> for _ in range(100):
... func()
>>> dis.dis(func, adaptive=True)
2 LOAD_SMALL_INT 1
STORE_FAST 0 (x)
3 LOAD_CONST_IMMORTAL 1 (Ellipsis)
STORE_FAST 1 (y)
4 LOAD_CONST 2 ('привет, мир')
STORE_FAST 2 (z)
LOAD_CONST_IMMORTAL 0 (None)
RETURN_VALUE
Зачем нужен LOAD_SMALL_INT?
https://github.com/python/cpython/issues/101291
Если вы внимательно смотрели мой видос про 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;
};
Большие и сложные объекты. Но, для очень частых маленьких чисел, такое переусложнение замедляет работу. Мы можем просто представлять числа в рамках одного машинного слова и складывать их сразу в oparg, без необходимости заргужать их из
co_consts:
op(_LOAD_SMALL_INT, (-- value)) {
PyObject *val = PyLong_FromLong(this_instr->oparg);
value = sym_new_const(ctx, val);
}
В Python2, кстати, работало быстрее, потому что там был честный
int тип.Обсуждение
Задумываетесь ли вы про подобные микро-оптимизации, когда пишите код?
GitHub
GH-125837: Split `LOAD_CONST` into three. by markshannon · Pull Request #125972 · python/cpython
Splits LOAD_CONST into three instructions
LOAD_INT for ints in range(256). Avoids the need for a space in the co_consts tuple and avoids an incref
LOAD_CONST_IMMORTAL for other immortal objects. A...
LOAD_INT for ints in range(256). Avoids the need for a space in the co_consts tuple and avoids an incref
LOAD_CONST_IMMORTAL for other immortal objects. A...
❤35👍9👌2
Argument Clinic
https://devguide.python.org/development-tools/clinic/
Если вы когда-нибудь смотрели исходники питона, то вы замечали внутри вот такие комментарии (взял за пример `sum()`):
Есть достаточно понятная проблема: нужно как-то иметь возможность передавать аргументы из Python кода в C код. Учитывая, что бывает много всяких видов Python и C функций (`METH_FASTCALL`,
AC позволяет делать достаточно просто описание сигнатуры функции при помощи специального DSL в комментариях.
И даже больше:
- Он генерирует сигнатуру сишной функции со всеми параметрами сразу после тега
- Он хранит последнее состояние в
- А еще он создает макросы вида:
Чтобы потом использовать их для добавления методов в модули / классы:
Как оно внутри?
- Есть большая либа внутри питона для работы с AC (с тестами и mypy)
- Есть make clinic для вызова данной либы на код, который вы меняете
- Можно кастомизировать выполнение либы на питоне, создавая питон код внутри C комментариев
- Мы используем AC даже для C-API тестов
- Сам генератор использует публичный C-API для выдергивания агрументов из переданных объектов. Код генерируется страшный, но читаемый, для примера кусок из файла
С ним значительно удобнее, чем писать такое руками!
---
Кстати, скоро мы с моими друзьями с Хабра делаем совместную движуху: https://vibe.habr.com/?utm_source=opensource_findings
В программе:
- Общение с разными ребятами, кто занимается карьерой
- Игра в карьерную настолку
- Специальные активности, чтобы понять, какие вайбы в работе подходят именно вам
https://devguide.python.org/development-tools/clinic/
Если вы когда-нибудь смотрели исходники питона, то вы замечали внутри вот такие комментарии (взял за пример `sum()`):
/*[clinic input]
sum as builtin_sum
iterable: object
/
start: object(c_default="NULL") = 0
Return the sum of a 'start' value (default: 0) plus an iterable of numbers.
[clinic start generated code]*/
static PyObject *
builtin_sum_impl(PyObject *module, PyObject *iterable, PyObject *start)
/*[clinic end generated code: output=df758cec7d1d302f input=162b50765250d222]*/
{
// ...
}
Есть достаточно понятная проблема: нужно как-то иметь возможность передавать аргументы из Python кода в C код. Учитывая, что бывает много всяких видов Python и C функций (`METH_FASTCALL`,
METH_O и тд), то все становится не так уж и просто.AC позволяет делать достаточно просто описание сигнатуры функции при помощи специального DSL в комментариях.
И даже больше:
- Он генерирует сигнатуру сишной функции со всеми параметрами сразу после тега
[clinic start generated code]- Он хранит последнее состояние в
/*[clinic end generated code: output=df758cec7d1d302f input=162b50765250d222]*/- А еще он создает макросы вида:
PyDoc_STRVAR(builtin_sum__doc__,
"sum($module, iterable, /, start=0)\n"
"--\n"
"\n"
"Return the sum of a \'start\' value (default: 0) plus an iterable of numbers");
#define BUILTIN_SUM_METHODDEF \
{"sum", _PyCFunction_CAST(builtin_sum), METH_FASTCALL|METH_KEYWORDS, builtin_sum__doc__},
Чтобы потом использовать их для добавления методов в модули / классы:
static PyMethodDef builtin_methods[] = {
BUILTIN_SUM_METHODDEF
// ...
};
Как оно внутри?
- Есть большая либа внутри питона для работы с AC (с тестами и mypy)
- Есть make clinic для вызова данной либы на код, который вы меняете
- Можно кастомизировать выполнение либы на питоне, создавая питон код внутри C комментариев
- Мы используем AC даже для C-API тестов
- Сам генератор использует публичный C-API для выдергивания агрументов из переданных объектов. Код генерируется страшный, но читаемый, для примера кусок из файла
Python/clinic/bltinmodule.c.h:
static PyObject *
builtin_sum(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
PyObject *return_value = NULL;
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
// ...
static const char * const _keywords[] = {"", "start", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "sum",
.kwtuple = KWTUPLE,
};
PyObject *argsbuf[2];
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1;
PyObject *iterable;
PyObject *start = NULL;
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 2, 0, argsbuf);
if (!args) {
goto exit;
}
iterable = args[0];
if (!noptargs) {
goto skip_optional_pos;
}
start = args[1];
skip_optional_pos:
return_value = builtin_sum_impl(module, iterable, start);
exit:
return return_value;
}
С ним значительно удобнее, чем писать такое руками!
---
Кстати, скоро мы с моими друзьями с Хабра делаем совместную движуху: https://vibe.habr.com/?utm_source=opensource_findings
В программе:
- Общение с разными ребятами, кто занимается карьерой
- Игра в карьерную настолку
- Специальные активности, чтобы понять, какие вайбы в работе подходят именно вам
2👍39❤9🤡2😱1