Skip to content

Commit cdcefcb

Browse files
Revert "gh-105936: Properly update closure cells for __setattr__ and __delattr__ in frozen dataclasses with slots (GH-144021)"
This reverts commit 8a398bf.
1 parent 882654a commit cdcefcb

3 files changed

Lines changed: 57 additions & 84 deletions

File tree

Lib/dataclasses.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -724,25 +724,25 @@ def _init_fn(fields, std_fields, kw_only_fields, frozen, has_post_init,
724724
annotation_fields=annotation_fields)
725725

726726

727-
def _frozen_set_del_attr(cls, fields, func_builder):
728-
locals = {'__class__': cls,
727+
def _frozen_get_del_attr(cls, fields, func_builder):
728+
locals = {'cls': cls,
729729
'FrozenInstanceError': FrozenInstanceError}
730-
condition = 'type(self) is __class__'
730+
condition = 'type(self) is cls'
731731
if fields:
732732
condition += ' or name in {' + ', '.join(repr(f.name) for f in fields) + '}'
733733

734734
func_builder.add_fn('__setattr__',
735735
('self', 'name', 'value'),
736736
(f' if {condition}:',
737737
' raise FrozenInstanceError(f"cannot assign to field {name!r}")',
738-
f' super(__class__, self).__setattr__(name, value)'),
738+
f' super(cls, self).__setattr__(name, value)'),
739739
locals=locals,
740740
overwrite_error=True)
741741
func_builder.add_fn('__delattr__',
742742
('self', 'name'),
743743
(f' if {condition}:',
744744
' raise FrozenInstanceError(f"cannot delete field {name!r}")',
745-
f' super(__class__, self).__delattr__(name)'),
745+
f' super(cls, self).__delattr__(name)'),
746746
locals=locals,
747747
overwrite_error=True)
748748

@@ -1205,7 +1205,7 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen,
12051205
overwrite_error='Consider using functools.total_ordering')
12061206

12071207
if frozen:
1208-
_frozen_set_del_attr(cls, field_list, func_builder)
1208+
_frozen_get_del_attr(cls, field_list, func_builder)
12091209

12101210
# Decide if/how we're going to create a hash function.
12111211
hash_action = _hash_action[bool(unsafe_hash),

Lib/test/test_dataclasses/__init__.py

Lines changed: 51 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -3052,41 +3052,29 @@ class C(base):
30523052

30533053

30543054
class TestFrozen(unittest.TestCase):
3055-
# Some tests have a subtest with a slotted dataclass.
3056-
# See https://github.com/python/cpython/issues/105936 for the reasons.
3057-
30583055
def test_frozen(self):
3059-
for slots in (False, True):
3060-
with self.subTest(slots=slots):
3061-
3062-
@dataclass(frozen=True, slots=slots)
3063-
class C:
3064-
i: int
3056+
@dataclass(frozen=True)
3057+
class C:
3058+
i: int
30653059

3066-
c = C(10)
3067-
self.assertEqual(c.i, 10)
3068-
with self.assertRaises(FrozenInstanceError):
3069-
c.i = 5
3070-
self.assertEqual(c.i, 10)
3071-
with self.assertRaises(FrozenInstanceError):
3072-
del c.i
3073-
self.assertEqual(c.i, 10)
3060+
c = C(10)
3061+
self.assertEqual(c.i, 10)
3062+
with self.assertRaises(FrozenInstanceError):
3063+
c.i = 5
3064+
self.assertEqual(c.i, 10)
30743065

30753066
def test_frozen_empty(self):
3076-
for slots in (False, True):
3077-
with self.subTest(slots=slots):
3078-
3079-
@dataclass(frozen=True, slots=slots)
3080-
class C:
3081-
pass
3067+
@dataclass(frozen=True)
3068+
class C:
3069+
pass
30823070

3083-
c = C()
3084-
self.assertNotHasAttr(c, 'i')
3085-
with self.assertRaises(FrozenInstanceError):
3086-
c.i = 5
3087-
self.assertNotHasAttr(c, 'i')
3088-
with self.assertRaises(FrozenInstanceError):
3089-
del c.i
3071+
c = C()
3072+
self.assertNotHasAttr(c, 'i')
3073+
with self.assertRaises(FrozenInstanceError):
3074+
c.i = 5
3075+
self.assertNotHasAttr(c, 'i')
3076+
with self.assertRaises(FrozenInstanceError):
3077+
del c.i
30903078

30913079
def test_inherit(self):
30923080
@dataclass(frozen=True)
@@ -3282,43 +3270,41 @@ class D(I):
32823270
d.i = 5
32833271

32843272
def test_non_frozen_normal_derived(self):
3285-
# See bpo-32953 and https://github.com/python/cpython/issues/105936
3286-
for slots in (False, True):
3287-
with self.subTest(slots=slots):
3273+
# See bpo-32953.
3274+
3275+
@dataclass(frozen=True)
3276+
class D:
3277+
x: int
3278+
y: int = 10
3279+
3280+
class S(D):
3281+
pass
32883282

3289-
@dataclass(frozen=True, slots=slots)
3290-
class D:
3291-
x: int
3292-
y: int = 10
3283+
s = S(3)
3284+
self.assertEqual(s.x, 3)
3285+
self.assertEqual(s.y, 10)
3286+
s.cached = True
32933287

3294-
class S(D):
3295-
pass
3288+
# But can't change the frozen attributes.
3289+
with self.assertRaises(FrozenInstanceError):
3290+
s.x = 5
3291+
with self.assertRaises(FrozenInstanceError):
3292+
s.y = 5
3293+
self.assertEqual(s.x, 3)
3294+
self.assertEqual(s.y, 10)
3295+
self.assertEqual(s.cached, True)
32963296

3297-
s = S(3)
3298-
self.assertEqual(s.x, 3)
3299-
self.assertEqual(s.y, 10)
3300-
s.cached = True
3301-
3302-
# But can't change the frozen attributes.
3303-
with self.assertRaises(FrozenInstanceError):
3304-
s.x = 5
3305-
with self.assertRaises(FrozenInstanceError):
3306-
s.y = 5
3307-
self.assertEqual(s.x, 3)
3308-
self.assertEqual(s.y, 10)
3309-
self.assertEqual(s.cached, True)
3310-
3311-
with self.assertRaises(FrozenInstanceError):
3312-
del s.x
3313-
self.assertEqual(s.x, 3)
3314-
with self.assertRaises(FrozenInstanceError):
3315-
del s.y
3316-
self.assertEqual(s.y, 10)
3317-
del s.cached
3318-
self.assertNotHasAttr(s, 'cached')
3319-
with self.assertRaises(AttributeError) as cm:
3320-
del s.cached
3321-
self.assertNotIsInstance(cm.exception, FrozenInstanceError)
3297+
with self.assertRaises(FrozenInstanceError):
3298+
del s.x
3299+
self.assertEqual(s.x, 3)
3300+
with self.assertRaises(FrozenInstanceError):
3301+
del s.y
3302+
self.assertEqual(s.y, 10)
3303+
del s.cached
3304+
self.assertNotHasAttr(s, 'cached')
3305+
with self.assertRaises(AttributeError) as cm:
3306+
del s.cached
3307+
self.assertNotIsInstance(cm.exception, FrozenInstanceError)
33223308

33233309
def test_non_frozen_normal_derived_from_empty_frozen(self):
33243310
@dataclass(frozen=True)
@@ -3985,14 +3971,6 @@ class SlotsTest:
39853971

39863972
return SlotsTest
39873973

3988-
# See https://github.com/python/cpython/issues/135228#issuecomment-3755979059
3989-
def make_frozen():
3990-
@dataclass(frozen=True, slots=True)
3991-
class SlotsTest:
3992-
pass
3993-
3994-
return SlotsTest
3995-
39963974
def make_with_annotations():
39973975
@dataclass(slots=True)
39983976
class SlotsTest:
@@ -4018,7 +3996,7 @@ class SlotsTest:
40183996

40193997
return SlotsTest
40203998

4021-
for make in (make_simple, make_frozen, make_with_annotations, make_with_annotations_and_method, make_with_forwardref):
3999+
for make in (make_simple, make_with_annotations, make_with_annotations_and_method, make_with_forwardref):
40224000
with self.subTest(make=make):
40234001
C = make()
40244002
support.gc_collect()

Misc/NEWS.d/next/Library/2026-01-19-21-23-18.gh-issue-105936.dGrzjM.rst

Lines changed: 0 additions & 5 deletions
This file was deleted.

0 commit comments

Comments
 (0)