TGViewer
Находки в опенсорсе Находки в опенсорсе @opensource_findings · 12.6K subscribers
Post #847 5.54K
`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)
(<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
  • 🤯 4
  • 🤡 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 →