From 0939d820fd0f62f020d6d8a03d09c4f7e808dff5 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Mon, 26 Aug 2024 18:38:09 +0300 Subject: [PATCH 1/2] gh-91126: Docs and tests for slotted dataclasses with `__init_subclass__` --- Doc/library/dataclasses.rst | 18 +++++++++++++---- Lib/test/test_dataclasses/__init__.py | 28 +++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/Doc/library/dataclasses.rst b/Doc/library/dataclasses.rst index fcb5e8bad295a0..3d9a2173a25a0e 100644 --- a/Doc/library/dataclasses.rst +++ b/Doc/library/dataclasses.rst @@ -185,10 +185,20 @@ Module contents - *slots*: If true (the default is ``False``), :attr:`~object.__slots__` attribute will be generated and new class will be returned instead of the original one. If :attr:`!__slots__` is already defined in the class, then :exc:`TypeError` - is raised. Calling no-arg :func:`super` in dataclasses using ``slots=True`` will result in - the following exception being raised: - ``TypeError: super(type, obj): obj must be an instance or subtype of type``. - The two-arg :func:`super` is a valid workaround. See :gh:`90562` for full details. + is raised. + + .. warning:: + Calling no-arg :func:`super` in dataclasses using ``slots=True`` + will result in the following exception being raised: + ``TypeError: super(type, obj): obj must be an instance or subtype of type``. + The two-arg :func:`super` is a valid workaround. + See :gh:`90562` for full details. + + .. warning:: + Having a base class with :meth:`~object.__init_subclass__` with parameters + when using ``slots=True`` will result in a :exc:`TypeError`. + Either use ``__init_subclass__`` with no parameters or use default values. + See :gh:`91126` for full details. .. versionadded:: 3.10 diff --git a/Lib/test/test_dataclasses/__init__.py b/Lib/test/test_dataclasses/__init__.py index b93c99d8c90bf3..6b62021ea075f4 100644 --- a/Lib/test/test_dataclasses/__init__.py +++ b/Lib/test/test_dataclasses/__init__.py @@ -3664,6 +3664,34 @@ class A(WithDictSlot): ... self.assertEqual(A().__dict__, {}) A() + def test_slots_with_wrong_init_subclass(self): + class WrongSuper: + def __init_subclass__(cls, arg): + pass + + with self.assertRaisesRegex( + TypeError, + "missing 1 required positional argument: 'arg'", + ): + @dataclass(slots=True) + class WithWrongSuper(WrongSuper, arg=1): + pass + + def test_slots_with_correct_init_subclass(self): + class CorrectSuper: + args = [] + def __init_subclass__(cls, arg="default"): + cls.args.append(arg) + + @dataclass(slots=True) + class WithCorrectSuper(CorrectSuper): + pass + + # __init_subclass__ is called twice: once for `WithCorrectSuper` + # and once for `WithCorrectSuper__slots__` new class + # that we create internally. + self.assertEqual(CorrectSuper.args, ["default", "default"]) + class TestDescriptors(unittest.TestCase): def test_set_name(self): From c5ac01613548345070fca054f9b41dc3afc42455 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Mon, 26 Aug 2024 22:07:28 +0300 Subject: [PATCH 2/2] Address review --- Doc/library/dataclasses.rst | 5 +++-- Lib/test/test_dataclasses/__init__.py | 6 +++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Doc/library/dataclasses.rst b/Doc/library/dataclasses.rst index 3d9a2173a25a0e..db1e6af530bdeb 100644 --- a/Doc/library/dataclasses.rst +++ b/Doc/library/dataclasses.rst @@ -195,9 +195,10 @@ Module contents See :gh:`90562` for full details. .. warning:: - Having a base class with :meth:`~object.__init_subclass__` with parameters + Passing parameters to a base class :meth:`~object.__init_subclass__` when using ``slots=True`` will result in a :exc:`TypeError`. - Either use ``__init_subclass__`` with no parameters or use default values. + Either use ``__init_subclass__`` with no parameters + or use default values as a workaround. See :gh:`91126` for full details. .. versionadded:: 3.10 diff --git a/Lib/test/test_dataclasses/__init__.py b/Lib/test/test_dataclasses/__init__.py index 6b62021ea075f4..da696ad961cfd7 100644 --- a/Lib/test/test_dataclasses/__init__.py +++ b/Lib/test/test_dataclasses/__init__.py @@ -3664,7 +3664,12 @@ class A(WithDictSlot): ... self.assertEqual(A().__dict__, {}) A() + @support.cpython_only def test_slots_with_wrong_init_subclass(self): + # TODO: This test is for a kinda-buggy behavior. + # Ideally, it should be fixed and `__init_subclass__` + # should be fully supported in the future versions. + # See https://github.com/python/cpython/issues/91126 class WrongSuper: def __init_subclass__(cls, arg): pass @@ -3677,7 +3682,6 @@ def __init_subclass__(cls, arg): class WithWrongSuper(WrongSuper, arg=1): pass - def test_slots_with_correct_init_subclass(self): class CorrectSuper: args = [] def __init_subclass__(cls, arg="default"):