From d86a8efb30e5c9c58054948c96037bcc4dc9af66 Mon Sep 17 00:00:00 2001 From: An Long Date: Thu, 3 Sep 2026 00:42:46 +0900 Subject: [PATCH 1/2] gh-154675: Avoid importing inspect when creating a slotted dataclass --- Lib/dataclasses.py | 20 +++++++++++++++++-- Lib/test/test_dataclasses/__init__.py | 16 ++++++++++++++- ...-09-03-00-42-32.gh-issue-154675.NX8eYm.rst | 2 ++ 3 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-09-03-00-42-32.gh-issue-154675.NX8eYm.rst diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index bd317aa9b0f2f3..af568e1f6dc519 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 a89999bb97938c..55a9ccd935d52b 100644 --- a/Lib/test/test_dataclasses/__init__.py +++ b/Lib/test/test_dataclasses/__init__.py @@ -27,7 +27,7 @@ import dataclasses # Needed for the string "dataclasses.InitVar[int]" to work as an annotation. from test import support -from test.support import cpython_only, import_helper +from test.support import cpython_only, import_helper, script_helper # Just any custom exception we can catch. class CustomError(Exception): pass @@ -41,6 +41,20 @@ def test_lazy_import(self): "dataclasses", {"inspect", "re", "copy"} ) + @cpython_only + def test_slots_does_not_import_inspect(self): + code = textwrap.dedent(""" + import sys + from dataclasses import dataclass + + @dataclass(slots=True) + class C: + x: int = 0 + + assert 'inspect' not in sys.modules + """) + script_helper.assert_python_ok("-c", code) + 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 00000000000000..f474a54c32b6b0 --- /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. From 2b98d017bbd117efb0f67a2e13de072e53a1c747 Mon Sep 17 00:00:00 2001 From: An Long Date: Thu, 3 Sep 2026 01:49:46 +0900 Subject: [PATCH 2/2] Use ensure_lazy_imports in the regression test --- Lib/test/test_dataclasses/__init__.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_dataclasses/__init__.py b/Lib/test/test_dataclasses/__init__.py index 55a9ccd935d52b..4a114d56fc3dfe 100644 --- a/Lib/test/test_dataclasses/__init__.py +++ b/Lib/test/test_dataclasses/__init__.py @@ -27,7 +27,7 @@ import dataclasses # Needed for the string "dataclasses.InitVar[int]" to work as an annotation. from test import support -from test.support import cpython_only, import_helper, script_helper +from test.support import cpython_only, import_helper # Just any custom exception we can catch. class CustomError(Exception): pass @@ -43,17 +43,17 @@ def test_lazy_import(self): @cpython_only def test_slots_does_not_import_inspect(self): - code = textwrap.dedent(""" - import sys - from dataclasses import dataclass - - @dataclass(slots=True) + create_slotted_class = textwrap.dedent( + """ + @dataclasses.dataclass(slots=True) class C: x: int = 0 - - assert 'inspect' not in sys.modules - """) - script_helper.assert_python_ok("-c", code) + """ + ) + import_helper.ensure_lazy_imports( + "dataclasses", {"inspect"}, + additional_code=create_slotted_class, + ) class TestCase(unittest.TestCase):