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 months ago
Your easy, fun crypto trading app for buying and trading any crypto on the market.
📱 App: @Blum
🆘 Help: @BlumSupport
ℹ️ Chat: @BlumCrypto_Chat
Last updated 3 months, 3 weeks ago
Turn your endless taps into a financial tool.
Join @tapswap_bot
Collaboration - @taping_Guru
Last updated 1 week, 1 day 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\_\_...
slots=True
ломает ваши датаклассы! Когда прям с заголовка набросил, то дальше уже всегда проще. Давайте посмотрим, какую пользу и какой вред приносит использование @dataclass(slots=True) или @attr.define(slots=True). В целом - различий не так много. Во…
slots=True
ломает ваши датаклассы! Когда прям с заголовка набросил, то дальше уже всегда проще. Давайте посмотрим, какую пользу и какой вред приносит использование @dataclass(slots=True) или @attr.define(slots=True). В целом - различий не так много. Во…
slots=True
ломает ваши датаклассы!
Когда прям с заголовка набросил, то дальше уже всегда проще.
Давайте посмотрим, какую пользу и какой вред приносит использование @dataclass(slots=True)
или @attr.define(slots=True)
. В целом - различий не так много.
Во-первых, что делает __slots__ = ('a',)
внутри класса?
```
class My:
__slots__ = ('a',)
```
__slots__
корректны __slots__
, см https://github.com/python/cpython/blob/91ff700de28f3415cbe44f58ce84a2670b8c9f15/Objects/descrobject.c#L793-L796 ```
class My:
... __slots__ = ('a',)
...
My.a, type(My.a)
(, )
```
__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
```
__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
Так что - будьте осторожны!
Проблемы модуля 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....
Одна из самых сложных частей в устройстве 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 для решения ваших бизнес задач? Помогает ли такой подход в написании более надежного кода?
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.
Сегодня говорим про 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
YouTube
Лучший курс по Python 8: bytes
Лучший курс по питону: 8 Или "обзор исходников CPython с CPython core разработчиком". Тема: bytes - Магические методы bytes: \_\_bytes\_\_, \_\_buffer\_\_, \_\_release\_buffer\_\_ - Способы записи bytes - bytes и collections.abc: Sequence, Buffer, bytes\_iterator - bytes…
История одного PR #prhistory
Некоторое время назад я добавил в mypy поддержку ReadOnly
special form для TypedDict
: https://github.com/python/mypy/pull/17644
PR большой, его будут смотреть еще некоторое время. Но, о самых важных принципах рассказать можно уже сейчас.
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
```
Крайне полезная вещь для бизнес логики.
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.
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'})
```
Помимо очевидного запрета на изменение 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.
Одна из самых проблемных частей 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
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:…
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 months ago
Your easy, fun crypto trading app for buying and trading any crypto on the market.
📱 App: @Blum
🆘 Help: @BlumSupport
ℹ️ Chat: @BlumCrypto_Chat
Last updated 3 months, 3 weeks ago
Turn your endless taps into a financial tool.
Join @tapswap_bot
Collaboration - @taping_Guru
Last updated 1 week, 1 day ago