Skip to content

Commit d86a8ef

Browse files
committed
gh-154675: Avoid importing inspect when creating a slotted dataclass
1 parent 2271fbb commit d86a8ef

3 files changed

Lines changed: 35 additions & 3 deletions

File tree

Lib/dataclasses.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1284,6 +1284,23 @@ def _get_slots(cls):
12841284
raise TypeError(f"Slots of '{cls.__name__}' cannot be determined")
12851285

12861286

1287+
def _unwrap(func):
1288+
# A copy of `inspect.unwrap()`, to avoid importing `inspect` module.
1289+
# Keep this in sync with the original.
1290+
f = func # remember the original func for error reporting
1291+
# Memoise by id to tolerate non-hashable objects, but store objects to
1292+
# ensure they aren't destroyed, which would allow their IDs to be reused.
1293+
memo = {id(f): f}
1294+
recursion_limit = sys.getrecursionlimit()
1295+
while not isinstance(func, type) and hasattr(func, '__wrapped__'):
1296+
func = func.__wrapped__
1297+
id_func = id(func)
1298+
if (id_func in memo) or (len(memo) >= recursion_limit):
1299+
raise ValueError(f'wrapper loop when unwrapping {f!r}')
1300+
memo[id_func] = func
1301+
return func
1302+
1303+
12871304
def _update_func_cell_for__class__(f, oldcls, newcls):
12881305
# Returns True if we update a cell, else False.
12891306
if f is None:
@@ -1392,8 +1409,7 @@ def _add_slots(cls, is_frozen, weakref_slot, defined_fields):
13921409

13931410
# If this is a wrapped function, unwrap it.
13941411
if not isinstance(member, type) and hasattr(member, '__wrapped__'):
1395-
import inspect
1396-
member = inspect.unwrap(member)
1412+
member = _unwrap(member)
13971413

13981414
if isinstance(member, types.FunctionType):
13991415
if _update_func_cell_for__class__(member, cls, newcls):

Lib/test/test_dataclasses/__init__.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
import dataclasses # Needed for the string "dataclasses.InitVar[int]" to work as an annotation.
2828

2929
from test import support
30-
from test.support import cpython_only, import_helper
30+
from test.support import cpython_only, import_helper, script_helper
3131

3232
# Just any custom exception we can catch.
3333
class CustomError(Exception): pass
@@ -41,6 +41,20 @@ def test_lazy_import(self):
4141
"dataclasses", {"inspect", "re", "copy"}
4242
)
4343

44+
@cpython_only
45+
def test_slots_does_not_import_inspect(self):
46+
code = textwrap.dedent("""
47+
import sys
48+
from dataclasses import dataclass
49+
50+
@dataclass(slots=True)
51+
class C:
52+
x: int = 0
53+
54+
assert 'inspect' not in sys.modules
55+
""")
56+
script_helper.assert_python_ok("-c", code)
57+
4458

4559
class TestCase(unittest.TestCase):
4660
def test_no_fields(self):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Creating a :func:`~dataclasses.dataclass` with ``slots=True`` no longer
2+
imports the :mod:`inspect` module.

0 commit comments

Comments
 (0)