Находки в опенсорсе

Description
Привет!

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

Тут я рассказываю про свои интересные проекты, коммиты, доклады, и тд.
Поддержать: https://boosty.to/sobolevn
Advertising
We recommend to visit

Community chat: https://t.me/hamster_kombat_chat_2

Twitter: x.com/hamster_kombat

YouTube: https://www.youtube.com/@HamsterKombat_Official

Bot: https://t.me/hamster_kombat_bot
Game: https://t.me/hamster_kombat_bot/

Last updated 4 weeks ago

Your easy, fun crypto trading app for buying and trading any crypto on the market

Last updated 3 weeks ago

Turn your endless taps into a financial tool.
Join @tapswap_bot


Collaboration - @taping_Guru

Last updated 1 week ago

4 weeks ago

Который предполагал, что если __slots__ явно не объявлены у типа, то по-умолчанию стоят __dict__ и __weakref__. Что правда для Python типов, но нельзя забывать про C типы, как я показывал выше. Я пофиксил вот так: https://github.com/python/cpython/commit…

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\_\_...

4 weeks ago

slots=True ломает ваши датаклассы! Когда прям с заголовка набросил, то дальше уже всегда проще. Давайте посмотрим, какую пользу и какой вред приносит использование @dataclass(slots=True) или @attr.define(slots=True). В целом - различий не так много. Во…

4 weeks ago

slots=True ломает ваши датаклассы! Когда прям с заголовка набросил, то дальше уже всегда проще. Давайте посмотрим, какую пользу и какой вред приносит использование @dataclass(slots=True) или @attr.define(slots=True). В целом - различий не так много. Во…

1 month ago

slots=True ломает ваши датаклассы!

Когда прям с заголовка набросил, то дальше уже всегда проще.

Давайте посмотрим, какую пользу и какой вред приносит использование @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)
(, )

```

  1. Если слот __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 "", line 1, in
AttributeError: 'My' object has no attribute 'custom'

```

Но, достаточно добавить '__dict__' внутрь __slots__, чтобы вернуть данное поведение: __slots__ = ('a', '__dict__'):

```

class My:
... __slots__ = ('a', '__dict__')
...
m = My()
m.custom = 0

```

  1. __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 "", line 1, in
str(My())
~~~^^^^^^
File "", 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

Так что - будьте осторожны!

1 month, 1 week ago

Проблемы модуля inspect.

Модуль inspect в питоне – сборник костылей и легаси.

Если вы не любите людей, то можете спрашивать их:
1. Чем отличается typing.get_type_hints от inspect.get_annotations? А от annotationslib.get_annotations?
2. Какие проблемы есть у getargvalues?
3. Чем отличаются getargs, getfullargspec и singature?
4. В чем разница между inspect.iscoroutinefunction и asyncio.iscoroutinefunction? А между inspect.iscoroutine и asyncio.iscoroutine?
5. Чем будет отличаться inspect.getmembers от inspect.getmembers_static?
6. Как конкретно работает получение сигнатуры у разных объектов? ?

Некоторое время назад я взялся исправить несколько самых сломанных частей: https://github.com/python/cpython/issues/108901

И даже сделал пакет с бекпортами для <=3.13: https://github.com/wemake-services/inspect313
Но все опять оказалось совсем не просто. Я не успел до фича фриза в 3.13, так что надеюсь, что успею в 3.14

Что сломано?

Например: inspect.getargvalues. Оно не работает с pos-only параметрами:

```

import inspect

def func(a: int = 0, /, b: int = 1, *, c: int = 2):
... return inspect.currentframe()

frame = func()
# notice that pos-only and kw-only args are not supported properly:
inspect.formatargvalues(*inspect.getargvalues(frame))
'(a=0, b=1, c=2)'

```

Должно быть так:

```

from inspect import Signature

str(Signature.from_frame(frame)) # this API does not exist yet
'(a=0, /, b=1, *, c=2)'

```

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

Далее: getfullargspec. Он не поддерживает pos-only параметры и не совсем корректно работает с параметрами self, cls, тд.

```

import inspect

class A:
... def method(self, arg, /): ...

inspect.getfullargspec(A.method)
FullArgSpec(args=['self', 'arg'], varargs=None, varkw=None, defaults=None, kwonlyargs=[], kwonlydefaults=None, annotations={})
inspect.getfullargspec(A().method).args # must not report self! :(
['self', 'arg']

inspect.signature(A.method)

inspect.signature(A().method)

```

Но, все-таки работа ведется довольно активно:
- asyncio.iscoroutinefunction уже задепрекейчена: https://github.com/python/cpython/pull/122875 Скоро будет только версия из inspect
- Добавили annotationslib.get_annotations (которая переехала из inspect и теперь будет самым-правильным-способом): https://github.com/python/cpython/blob/9e108b8719752a0a2e390eeeaa8f52391f75120d/Lib/annotationlib.py#L582
- Пофиксили кучу багов

Для чего inspect можно использовать на практике?

Я пользовался inspect.signature только для создания рантайм имплементациия каррирования для dry\-python/returns: https://github.com/dry-python/returns/blob/master/returns/curry.py

Довольно много библиотечного кода используют inspect для интроспекции в самых неожиданных местах:
- https://github.com/search?type=code&q=inspect.iscoroutinefunction
- https://github.com/search?type=code&q=inspect.getfullargspec
- https://github.com/search?type=code&q=inspect.getargvalues

Расскажите: а у вас были проблемы с inspect? Если да, то какие?

GitHub

Add modern alternatives to `inspect` module, deprecate old incorrect APIs · Issue #108901 · python/cpython

Feature or enhancement Proposal: I propose to provide modern alternatives to and deprecate these inspect members: getargs() undocumented helper used in getargvalues. It works with \_\_code\_\_ objects....

1 month, 2 weeks ago

Одна из самых сложных частей в устройстве mypy – type narrowing.

Что такое type narrowing? По-русски оно называется "сужение типа". Например:

```

def accepts_both(arg: int | str):
reveal_type(arg) # int | str
if isinstance(arg, int):
reveal_type(arg) # int
else:
reveal_type(arg) # str

```

Тут все достаточно очевидно. Следующие важные части для сужения типов:
- TypeGuard – определяет, каким будет тип в if при вызове функции-проверки. Почти не имеет ограничений.
- TypeIs – определяет, каким будет тип в if при вызове функции-проверки и что будет в else. Имеет множество ограничений.

Пример:

```

from typing import TypeIs

from my_project import Schema

def is_schema(obj: object) -> TypeIs[Schema]:
return hasattr(obj, "_schema") # actually returns bool

def accepts_both(obj: str | Schema):
reveal_type(arg) # str | Schema
if is_schema(arg):
reveal_type(arg) # Schema
else:
reveal_type(arg) # str

```

Есть еще много разных механизмов для сужения, подробно можно посмотреть тут: https://github.com/python/mypy/blob/fe15ee69b9225f808f8ed735671b73c31ae1bed8/mypy/checker.py#L5805

Но сейчас поговорим про новую фичу, которая скоро появится в mypy. TypeIs / TypeGuard + @overload ?

Допустим у вас есть такой код:

```

from typing import Any, TypeIs, overload

@overload
def func1(x: str) -> TypeIs[str]:
...

@overload
def func1(x: int) -> TypeIs[int]:
...

def func1(x: Any) -> Any:
return True # does not matter

```

После моего PR (https://github.com/python/mypy/pull/17678) вызов данной функции и сужение типа будут работать корректно:

```

def accepts_both(val: Any):
if func1(val):
reveal_type(val) # N: Revealed type is "Union[builtins.int, builtins.str]"
else:
reveal_type(val) # N: Revealed type is "Any"

```

Данная функция, например, используется для типизации встроенной функции dataclasses.is_dataclass: https://github.com/python/typeshed/blob/53be87bbb45d0b294a4f5b12683e7684e20032d9/stdlib/dataclasses.pyi#L217-L223

Сейчас ей требуется грязный хак с Never в первом @overload:

```

# HACK: obj: Never typing matches if object argument is using Any type.
@overload
def is_dataclass(obj: Never) -> TypeIs[DataclassInstance | type[DataclassInstance]]: ... # type: ignore[narrowed-type-not-subtype] # pyright: ignore[reportGeneralTypeIssues]
@overload
def is_dataclass(obj: type) -> TypeIs[type[DataclassInstance]]: ...
@overload
def is_dataclass(obj: object) -> TypeIs[DataclassInstance | type[DataclassInstance]]: ...

```

Дальше – я его уберу.

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

```

from typing import TypeIs, overload

from my_app.models import User, PaidUser, FreeUser # User and its subclasses
from my_app.models import Subscription

@overload
def is_paid_user(user: User, subscription: None) -> TypeIs[FreeUser]:
...

@overload
def is_paid_user(user: User, subscription: Subscription) -> TypeIs[PaidUser]:
...

```

В комментах можно обсудить: приходилось ли вам использовать type narrowing для решения ваших бизнес задач? Помогает ли такой подход в написании более надежного кода?

#prhistory

GitHub

mypy/mypy/checker.py at fe15ee69b9225f808f8ed735671b73c31ae1bed8 · python/mypy

Optional static typing for Python. Contribute to python/mypy development by creating an account on GitHub.

Одна из самых сложных частей в устройстве mypy – type narrowing.
1 month, 3 weeks ago

Сегодня говорим про bytes!

Вышел восьмой урок "Лучшего курса по Питону": https://www.youtube.com/watch?v=RbznhbK3vC0

Что вообще такое "Лучший курс по Питону"?
- Я решил разобрать все исходники CPython и показать, как на самом деле работают все его части
- В каждом видео я рассказываю про одну узкую тему
- Каждое видео я делю на три уровня сложности: для джунов, мидлов и сениоров
- Переодически беру интервью у других core-разработчиков CPython про разные части интерпретатора в их зоне интересов
- Получается очень хардкорно!

Например, в bytes я показываю, как устроен PyBytesObject (он простой):

```

typedef struct {
PyObject_VAR_HEAD
Py_DEPRECATED(3.11) Py_hash_t ob_shash;
char ob_sval[1];

/* Invariants: * ob\_sval contains space for 'ob\_size+1' elements. * ob\_sval[ob\_size] == 0. * ob\_shash is the hash of the byte string or \-1 if not computed yet. */

} PyBytesObject;

```

Как устроен Buffer протокол для bytes с его __buffer__ и __release_buffer__:

```

static int
bytes_buffer_getbuffer(PyBytesObject self, Py_buffer view, int flags)
{
return PyBuffer_FillInfo(view, (PyObject)self, (void )self->ob_sval, Py_SIZE(self), 1, flags);
}

static PyBufferProcs bytes_as_buffer = {
(getbufferproc)bytes_buffer_getbuffer,
NULL,
};

```

Дополнительные материалы (не вошли в выпуск):
- mypy: bytes и bytearray c disable_bytearray_promotion и disable_memoryview_promotion https://github.com/python/mypy/commit/2d70ac0b33b448d5ef51c0856571068dd0754af6
- Мутабельность bytes https://docs.python.org/3.13/c-api/bytes.html#c._PyBytes_Resize
- PyBytes_Writer API: https://github.com/capi-workgroup/decisions/issues/39
- ob_shash deprecation: https://github.com/python/cpython/issues/91020

Поддержать проект можно тут: https://boosty.to/sobolevn

#лкпп #python #c

YouTube

Лучший курс по Python 8: bytes

Лучший курс по питону: 8 Или "обзор исходников CPython с CPython core разработчиком". Тема: bytes - Магические методы bytes: \_\_bytes\_\_, \_\_buffer\_\_, \_\_release\_buffer\_\_ - Способы записи bytes - bytes и collections.abc: Sequence, Buffer, bytes\_iterator - bytes…

1 month, 3 weeks ago

История одного PR #prhistory

Некоторое время назад я добавил в mypy поддержку ReadOnly special form для TypedDict: https://github.com/python/mypy/pull/17644
PR большой, его будут смотреть еще некоторое время. Но, о самых важных принципах рассказать можно уже сейчас.

  1. Что такое ReadOnly?

PEP: https://peps.python.org/pep-0705

По сути, он запрещает такой код:

```

from typing import ReadOnly, TypedDict

class User(TypedDict):
username: ReadOnly[str] # you cannot change the value of user['username']

user: User = {'username': 'sobolevn'}
user['username'] = 'changed' # type error! username is read-only

```

Крайне полезная вещь для бизнес логики.

  1. ReadOnly был добавлен в Python в версию 3.13 мной некоторое время назад: https://github.com/python/cpython/pull/116350

Однако, он был добавлен в typing_extensions еще раньше: https://github.com/python/typing_extensions/commit/0b0166d649cebcb48e7e208ae5da36cfab5965fe
Так что пользоваться typing_extensions.ReadOnly можно будет как только выйдет новая версия mypy с поддержкой данной special form.

  1. Как устроен ReadOnly?

Основная сложность, что разные special form'ы могут идти вместе:
- username: ReadOnly[Required[str]]
- age: NotRequired[Annotated[ReadOnly[int], Validate(min=18, max=120)]]
- и тд в любых комбинациях

Внутри TypedDict появились специальные атрибуты: __readonly_keys__ и __mutable_keys__:

```

from typing import TypedDict, ReadOnly
class User(TypedDict):
... username: ReadOnly[str]
... age: int
...
User.__readonly_keys__
frozenset({'username'})
User.__mutable_keys__
frozenset({'age'})

```

  1. Какие делатали типизации важны?

Помимо очевидного запрета на изменение ReadOnly ключей, нужно помнить, про отношение подтипов.

Пример:

```

User = TypedDict('User', {'username': ReadOnly[str]})
MutableUser = TypedDict('MutableUser', {'username': str})

def accepts_user(user: User): ...
def accepts_mutable_user(user: MutableUser): ...

ro_user: User
mut_user: MutableUser

# MutableUser является подвидом User, но User не является подвидом MutableUser
accepts_user(mut_user) # ok
accepts_mutable_user(ro_user) # error: expected: MutableUser, given: User

```

Но почему?
Потому что тело функции accepts_mutable_user может выглядеть как-то так:

```

def accepts_mutable_user(user: MutableUser):
user['username'] = ''

```

Таким образом – мы могли бы допустить ошибку и изменить "неизменяемый" ключ.

Ждём? ?

GitHub

Add `ReadOnly` support for TypedDicts by sobolevn · Pull Request #17644 · python/mypy

Refs #17264 I will add docs in a separate PR.

1 month, 4 weeks ago

Одна из самых проблемных частей CPython – вызов Python кода из С.

Делать такое нужно довольно регулярно. Примеры использований:
- Обращение к магическим методам объектов: PyObject_RichCompare, PyObject_GetIter, PyIter_Next, PyObject_GetItem, и тд
- Вызов переданных Python callback'ов: PyObject_Call*, PyObject_Vectorcall, и тд
- Создание новых объектов: PyObject_New

Но, такое всегда нужно делать осторожно. Буквально, почти весь стейт внутри C может измениться после вызова любого Python кода!

Например, такой простой код вызовет [1] 88503 segmentation fault python на версиях <3.12.5

```

class evil:
def __init__(self, lst):
self.lst = lst
def __iter__(self):
yield from self.lst
self.lst.clear()

lst = list(range(10))
lst[::-1] = evil(lst)

```

Мне нравится править такое, одно из самых интересных направлений:
- https://github.com/python/cpython/pull/120442
- https://github.com/python/cpython/pull/120303

А вот как такое находить?
1. Внутри CPython есть свой фаззер: https://github.com/python/cpython/tree/main/Modules/_xxtestfuzz Иногда он находит код, который крашит какой-то кусок. Было довольно много полезных срабатываний
2. Есть отдельные инструменты и команды по всему миру, кто заинтересован в разметке исходников CPython и выявлении таких проблем статически
3. Собирать баги от пользователей :(

Если видите crash – бегом репортить багу!

GitHub

gh-120384: Fix array-out-of-bounds crash in `list_ass_subscript` by sobolevn · Pull Request #120442 · python/cpython

Thank a lot to @MechanicPig for the reproducer and detailed bug description. Issue: Array out of bounds assignment in list\_ass\_subscript #120384

6 months ago

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

YouTube

ЛКПП 1: int

Лучший курс по питону: 1 int: магические методы, абстрактные базовые классы (numeric tower), внутреннее устройство. 00:00 Junior 07:43 Middle 12:50 Senior Полезные ссылки: - Все материалы: https://github.com/sobolevn/the-best-python-course - Мой GitHub:…

We recommend to visit

Community chat: https://t.me/hamster_kombat_chat_2

Twitter: x.com/hamster_kombat

YouTube: https://www.youtube.com/@HamsterKombat_Official

Bot: https://t.me/hamster_kombat_bot
Game: https://t.me/hamster_kombat_bot/

Last updated 4 weeks ago

Your easy, fun crypto trading app for buying and trading any crypto on the market

Last updated 3 weeks ago

Turn your endless taps into a financial tool.
Join @tapswap_bot


Collaboration - @taping_Guru

Last updated 1 week ago