gh-105936: Properly update closure cells for `__setattr__` and `__del… · python/cpython@8a398bf

@@ -3052,29 +3052,41 @@ class C(base):

305230523053305330543054

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+30553058

def test_frozen(self):

3056-

@dataclass(frozen=True)

3057-

class C:

3058-

i: int

3059+

for slots in (False, True):

3060+

with self.subTest(slots=slots):

305930613060-

c = C(10)

3061-

self.assertEqual(c.i, 10)

3062-

with self.assertRaises(FrozenInstanceError):

3063-

c.i = 5

3064-

self.assertEqual(c.i, 10)

3062+

@dataclass(frozen=True, slots=slots)

3063+

class C:

3064+

i: int

3065+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)

3065307430663075

def test_frozen_empty(self):

3067-

@dataclass(frozen=True)

3068-

class C:

3069-

pass

3076+

for slots in (False, True):

3077+

with self.subTest(slots=slots):

307030783071-

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

3079+

@dataclass(frozen=True, slots=slots)

3080+

class C:

3081+

pass

3082+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

3078309030793091

def test_inherit(self):

30803092

@dataclass(frozen=True)

@@ -3270,41 +3282,43 @@ class D(I):

32703282

d.i = 5

3271328332723284

def test_non_frozen_normal_derived(self):

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

3285+

# See bpo-32953 and https://github.com/python/cpython/issues/105936

3286+

for slots in (False, True):

3287+

with self.subTest(slots=slots):

328232883283-

s = S(3)

3284-

self.assertEqual(s.x, 3)

3285-

self.assertEqual(s.y, 10)

3286-

s.cached = True

3289+

@dataclass(frozen=True, slots=slots)

3290+

class D:

3291+

x: int

3292+

y: int = 10

328732933288-

# 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)

3294+

class S(D):

3295+

pass

329632963297-

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)

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)

3308332233093323

def test_non_frozen_normal_derived_from_empty_frozen(self):

33103324

@dataclass(frozen=True)

@@ -3971,6 +3985,14 @@ class SlotsTest:

3971398539723986

return SlotsTest

397339873988+

# 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+39743996

def make_with_annotations():

39753997

@dataclass(slots=True)

39763998

class SlotsTest:

@@ -3996,7 +4018,7 @@ class SlotsTest:

3996401839974019

return SlotsTest

399840203999-

for make in (make_simple, make_with_annotations, make_with_annotations_and_method, make_with_forwardref):

4021+

for make in (make_simple, make_frozen, make_with_annotations, make_with_annotations_and_method, make_with_forwardref):

40004022

with self.subTest(make=make):

40014023

C = make()

40024024

support.gc_collect()