Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
gh-154675: Avoid importing inspect when creating a slotted dataclass
  • Loading branch information
aisk committed Sep 2, 2026
commit d86a8efb30e5c9c58054948c96037bcc4dc9af66
20 changes: 18 additions & 2 deletions Lib/dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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):
Expand Down
16 changes: 15 additions & 1 deletion Lib/test/test_dataclasses/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Creating a :func:`~dataclasses.dataclass` with ``slots=True`` no longer
imports the :mod:`inspect` module.
Loading