Skip to content

Commit 0aa2387

Browse files
committed
Create the slotted class before generating methods
1 parent 2b98d01 commit 0aa2387

2 files changed

Lines changed: 15 additions & 32 deletions

File tree

Lib/dataclasses.py

Lines changed: 13 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -561,8 +561,7 @@ def __annotate__(format, /):
561561
case _:
562562
raise NotImplementedError(format)
563563

564-
# This is a flag for _add_slots to know it needs to regenerate this method
565-
# In order to remove references to the original class when it is replaced
564+
# Mark the function as generated by dataclasses, for introspection.
566565
__annotate__.__generated_by_dataclasses__ = True
567566
__annotate__.__qualname__ = f"{__class__.__qualname__}.{method_name}.__annotate__"
568567

@@ -1118,6 +1117,16 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen,
11181117
# also marks this class as being a dataclass.
11191118
setattr(cls, _FIELDS, fields)
11201119

1120+
# It's an error to specify weakref_slot if slots is False.
1121+
if weakref_slot and not slots:
1122+
raise TypeError('weakref_slot is True but slots is False')
1123+
if slots:
1124+
# Create the slotted class before generating any methods, so that
1125+
# the generated methods are bound to the final class from the start
1126+
# and the __class__ cell fix-up in _add_slots only has to look at
1127+
# user-defined methods.
1128+
cls = _add_slots(cls, frozen, weakref_slot, fields)
1129+
11211130
# Was this class defined with an explicit __hash__? Note that if
11221131
# __eq__ is defined in this class, then python will automatically
11231132
# set __hash__ to None. This is a heuristic, as it's possible
@@ -1235,12 +1244,6 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen,
12351244
_set_new_attribute(cls, '__match_args__',
12361245
tuple(f.name for f in std_init_fields))
12371246

1238-
# It's an error to specify weakref_slot if slots is False.
1239-
if weakref_slot and not slots:
1240-
raise TypeError('weakref_slot is True but slots is False')
1241-
if slots:
1242-
cls = _add_slots(cls, frozen, weakref_slot, fields)
1243-
12441247
abc.update_abstractmethods(cls)
12451248

12461249
return cls
@@ -1284,23 +1287,6 @@ def _get_slots(cls):
12841287
raise TypeError(f"Slots of '{cls.__name__}' cannot be determined")
12851288

12861289

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-
13041290
def _update_func_cell_for__class__(f, oldcls, newcls):
13051291
# Returns True if we update a cell, else False.
13061292
if f is None:
@@ -1409,7 +1395,8 @@ def _add_slots(cls, is_frozen, weakref_slot, defined_fields):
14091395

14101396
# If this is a wrapped function, unwrap it.
14111397
if not isinstance(member, type) and hasattr(member, '__wrapped__'):
1412-
member = _unwrap(member)
1398+
import inspect
1399+
member = inspect.unwrap(member)
14131400

14141401
if isinstance(member, types.FunctionType):
14151402
if _update_func_cell_for__class__(member, cls, newcls):
@@ -1434,12 +1421,6 @@ def _add_slots(cls, is_frozen, weakref_slot, defined_fields):
14341421
else:
14351422
f.type = ann
14361423

1437-
# Fix the class reference in the __annotate__ method
1438-
init = newcls.__init__
1439-
if init_annotate := getattr(init, "__annotate__", None):
1440-
if getattr(init_annotate, "__generated_by_dataclasses__", False):
1441-
_update_func_cell_for__class__(init_annotate, cls, newcls)
1442-
14431424
return newcls
14441425

14451426

Lib/test/test_dataclasses/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ def test_lazy_import(self):
4343

4444
@cpython_only
4545
def test_slots_does_not_import_inspect(self):
46+
# inspect is only needed to unwrap user-defined methods
47+
# that are wrapped by a decorator.
4648
create_slotted_class = textwrap.dedent(
4749
"""
4850
@dataclasses.dataclass(slots=True)

0 commit comments

Comments
 (0)