From 7c936b01c4c3b2fb81c6e87481f3a4b7a1c22488 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Tue, 19 May 2026 12:44:55 +0300 Subject: [PATCH 1/2] gh-150069: Test frozen dataclass and properties interaction --- Lib/test/test_dataclasses/__init__.py | 71 ++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_dataclasses/__init__.py b/Lib/test/test_dataclasses/__init__.py index 2468e3e64dd621c..0e57bc4748a6bf4 100644 --- a/Lib/test/test_dataclasses/__init__.py +++ b/Lib/test/test_dataclasses/__init__.py @@ -3328,6 +3328,47 @@ def test_non_frozen_normal_derived(self): class D: x: int y: int = 10 + z: int = 0 + + @property + def readonly(self) -> int: + return self.x + + @property + def prop(self) -> int: + return self.z + + @prop.setter + def prop(self, val: int) -> None: + object.__setattr__(self, 'z', val) + + @prop.deleter + def prop(self) -> None: + object.__setattr__(self, 'z', 0) + + d = D(5) + self.assertEqual(d.x, 5) + self.assertEqual(d.y, 10) + self.assertEqual(d.z, 0) + self.assertEqual(d.readonly, 5) + self.assertEqual(d.prop, 0) + + with self.assertRaises(FrozenInstanceError): + d.x = 5 + with self.assertRaises(FrozenInstanceError): + d.readonly = 5 + with self.assertRaises(FrozenInstanceError): + d.z = 5 + with self.assertRaises(FrozenInstanceError): + d.prop = 5 + with self.assertRaises(FrozenInstanceError): + del d.prop + + self.assertEqual(d.x, 5) + self.assertEqual(d.y, 10) + self.assertEqual(d.z, 0) + self.assertEqual(d.readonly, 5) + self.assertEqual(d.prop, 0) class S(D): pass @@ -3335,16 +3376,35 @@ class S(D): s = S(3) self.assertEqual(s.x, 3) self.assertEqual(s.y, 10) + self.assertEqual(s.z, 0) + self.assertEqual(s.readonly, 3) + self.assertEqual(s.prop, 0) + # Can set new attrs: s.cached = True + # Can mutate them: + s.cached = False + + # Can also change writable properties: + with self.assertRaises(AttributeError) as cm: + s.readonly = 5 + self.assertNotIsInstance(cm.exception, FrozenInstanceError) + s.prop = 1 + self.assertEqual(s.x, 3) + self.assertEqual(s.readonly, 3) + self.assertEqual(s.prop, 1) + self.assertEqual(s.z, 1) # But can't change the frozen attributes. with self.assertRaises(FrozenInstanceError): s.x = 5 with self.assertRaises(FrozenInstanceError): s.y = 5 + with self.assertRaises(FrozenInstanceError): + s.z = 5 self.assertEqual(s.x, 3) self.assertEqual(s.y, 10) - self.assertEqual(s.cached, True) + self.assertEqual(s.z, 1) + self.assertIs(s.cached, False) with self.assertRaises(FrozenInstanceError): del s.x @@ -3352,11 +3412,20 @@ class S(D): with self.assertRaises(FrozenInstanceError): del s.y self.assertEqual(s.y, 10) + with self.assertRaises(AttributeError) as cm: + del s.readonly + self.assertNotIsInstance(cm.exception, FrozenInstanceError) + self.assertEqual(s.x, 3) + self.assertEqual(s.readonly, 3) del s.cached self.assertNotHasAttr(s, 'cached') with self.assertRaises(AttributeError) as cm: del s.cached self.assertNotIsInstance(cm.exception, FrozenInstanceError) + del s.prop + self.assertEqual(s.z, 0) + self.assertEqual(s.prop, 0) + del s.prop def test_non_frozen_normal_derived_from_empty_frozen(self): @dataclass(frozen=True) From a367bead250f80c8418bef84b89c08d3d04c73f6 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 2 Jul 2026 15:20:04 +0300 Subject: [PATCH 2/2] Address review --- Lib/test/test_dataclasses/__init__.py | 39 +++++++++++++++++---------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/Lib/test/test_dataclasses/__init__.py b/Lib/test/test_dataclasses/__init__.py index ed3112d05f6a31d..a89999bb97938c0 100644 --- a/Lib/test/test_dataclasses/__init__.py +++ b/Lib/test/test_dataclasses/__init__.py @@ -3377,7 +3377,7 @@ def test_non_frozen_normal_derived(self): class D: x: int y: int = 10 - z: int = 0 + z: int = 1 @property def readonly(self) -> int: @@ -3398,9 +3398,9 @@ def prop(self) -> None: d = D(5) self.assertEqual(d.x, 5) self.assertEqual(d.y, 10) - self.assertEqual(d.z, 0) + self.assertEqual(d.z, 1) self.assertEqual(d.readonly, 5) - self.assertEqual(d.prop, 0) + self.assertEqual(d.prop, 1) with self.assertRaises(FrozenInstanceError): d.x = 5 @@ -3415,9 +3415,9 @@ def prop(self) -> None: self.assertEqual(d.x, 5) self.assertEqual(d.y, 10) - self.assertEqual(d.z, 0) + self.assertEqual(d.z, 1) self.assertEqual(d.readonly, 5) - self.assertEqual(d.prop, 0) + self.assertEqual(d.prop, 1) class S(D): pass @@ -3425,23 +3425,28 @@ class S(D): s = S(3) self.assertEqual(s.x, 3) self.assertEqual(s.y, 10) - self.assertEqual(s.z, 0) + self.assertEqual(s.z, 1) self.assertEqual(s.readonly, 3) - self.assertEqual(s.prop, 0) + self.assertEqual(s.prop, 1) # Can set new attrs: s.cached = True + self.assertTrue(s.cached) # Can mutate them: s.cached = False + self.assertFalse(s.cached) # Can also change writable properties: - with self.assertRaises(AttributeError) as cm: + with self.assertRaisesRegex( + AttributeError, + 'object has no setter', + ) as cm: s.readonly = 5 self.assertNotIsInstance(cm.exception, FrozenInstanceError) - s.prop = 1 + s.prop = 2 self.assertEqual(s.x, 3) self.assertEqual(s.readonly, 3) - self.assertEqual(s.prop, 1) - self.assertEqual(s.z, 1) + self.assertEqual(s.prop, 2) + self.assertEqual(s.z, 2) # But can't change the frozen attributes. with self.assertRaises(FrozenInstanceError): @@ -3452,7 +3457,7 @@ class S(D): s.z = 5 self.assertEqual(s.x, 3) self.assertEqual(s.y, 10) - self.assertEqual(s.z, 1) + self.assertEqual(s.z, 2) self.assertIs(s.cached, False) with self.assertRaises(FrozenInstanceError): @@ -3461,14 +3466,20 @@ class S(D): with self.assertRaises(FrozenInstanceError): del s.y self.assertEqual(s.y, 10) - with self.assertRaises(AttributeError) as cm: + with self.assertRaisesRegex( + AttributeError, + 'object has no deleter', + ) as cm: del s.readonly self.assertNotIsInstance(cm.exception, FrozenInstanceError) self.assertEqual(s.x, 3) self.assertEqual(s.readonly, 3) del s.cached self.assertNotHasAttr(s, 'cached') - with self.assertRaises(AttributeError) as cm: + with self.assertRaisesRegex( + AttributeError, + "object has no attribute 'cached'", + ) as cm: del s.cached self.assertNotIsInstance(cm.exception, FrozenInstanceError) del s.prop