TGViewer
Находки в опенсорсе Находки в опенсорсе @opensource_findings · 12.6K subscribers
Post #849 4.86K
Находки в опенсорсе `slots=True` ломает ваши датаклассы! Когда прям с заголовка набросил, то дальше уже всегда проще. Давайте посмотрим, какую пользу и какой вред приносит использование @dataclass(slots=True) или @attr.define(slots=True). В целом - различий не так много. Во…
Продолжаем ломать dataclass'ы со `__slots__`!

Некоторое время назад прилетел баг: 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
More from @opensource_findings
  1. Sep 28, 2026Еще анонсы докладов на бесплатную конференцию 17 октября в НН Регистрация: https://itgorky…
  2. Sep 25, 2026Монадические выражения в Python. Наконец-то! Продолжаем разговор про интересные PEPы. Пого…
  3. Sep 15, 2026PEPы в Python окончательно вышли из-под контроля Давайте посмотрим, что происходит с ПЕПам…
  4. Sep 10, 2026Большая бесплатная конференция в Нижнем Новгороде 17 октября Регистрация: https://itgorky.…
  5. Sep 1, 2026Post #985
  6. Aug 24, 2026JIT могут удалить из CPython! PEP-836: https://peps.python.org/pep-0836 JIT в CPython имее…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →