diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index bd317aa9b0f2f37..af568e1f6dc5192 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -1284,6 +1284,23 @@ def _get_slots(cls): raise TypeError(f"Slots of '{cls.__name__}' cannot be determined") +def _unwrap(func): + # A copy of `inspect.unwrap()`, to avoid importing `inspect` module. + # Keep this in sync with the original. + f = func # remember the original func for error reporting + # Memoise by id to tolerate non-hashable objects, but store objects to + # ensure they aren't destroyed, which would allow their IDs to be reused. + memo = {id(f): f} + recursion_limit = sys.getrecursionlimit() + while not isinstance(func, type) and hasattr(func, '__wrapped__'): + func = func.__wrapped__ + id_func = id(func) + if (id_func in memo) or (len(memo) >= recursion_limit): + raise ValueError(f'wrapper loop when unwrapping {f!r}') + memo[id_func] = func + return func + + def _update_func_cell_for__class__(f, oldcls, newcls): # Returns True if we update a cell, else False. if f is None: @@ -1392,8 +1409,7 @@ def _add_slots(cls, is_frozen, weakref_slot, defined_fields): # If this is a wrapped function, unwrap it. if not isinstance(member, type) and hasattr(member, '__wrapped__'): - import inspect - member = inspect.unwrap(member) + member = _unwrap(member) if isinstance(member, types.FunctionType): if _update_func_cell_for__class__(member, cls, newcls): diff --git a/Lib/test/test_dataclasses/__init__.py b/Lib/test/test_dataclasses/__init__.py index a89999bb97938c0..4a114d56fc3dfe5 100644 --- a/Lib/test/test_dataclasses/__init__.py +++ b/Lib/test/test_dataclasses/__init__.py @@ -41,6 +41,20 @@ def test_lazy_import(self): "dataclasses", {"inspect", "re", "copy"} ) + @cpython_only + def test_slots_does_not_import_inspect(self): + create_slotted_class = textwrap.dedent( + """ + @dataclasses.dataclass(slots=True) + class C: + x: int = 0 + """ + ) + import_helper.ensure_lazy_imports( + "dataclasses", {"inspect"}, + additional_code=create_slotted_class, + ) + class TestCase(unittest.TestCase): def test_no_fields(self): diff --git a/Misc/NEWS.d/next/Library/2026-09-03-00-42-32.gh-issue-154675.NX8eYm.rst b/Misc/NEWS.d/next/Library/2026-09-03-00-42-32.gh-issue-154675.NX8eYm.rst new file mode 100644 index 000000000000000..f474a54c32b6b0a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-09-03-00-42-32.gh-issue-154675.NX8eYm.rst @@ -0,0 +1,2 @@ +Creating a :func:`~dataclasses.dataclass` with ``slots=True`` no longer +imports the :mod:`inspect` module.