annotation_fields=annotation_fields)
-def _frozen_get_del_attr(cls, fields, func_builder):
- locals = {'cls': cls,
+def _frozen_set_del_attr(cls, fields, func_builder):
+ locals = {'__class__': cls,
'FrozenInstanceError': FrozenInstanceError}
- condition = 'type(self) is cls'
+ condition = 'type(self) is __class__'
if fields:
condition += ' or name in {' + ', '.join(repr(f.name) for f in fields) + '}'
('self', 'name', 'value'),
(f' if {condition}:',
' raise FrozenInstanceError(f"cannot assign to field {name!r}")',
- f' super(cls, self).__setattr__(name, value)'),
+ f' super(__class__, self).__setattr__(name, value)'),
locals=locals,
overwrite_error=True)
func_builder.add_fn('__delattr__',
('self', 'name'),
(f' if {condition}:',
' raise FrozenInstanceError(f"cannot delete field {name!r}")',
- f' super(cls, self).__delattr__(name)'),
+ f' super(__class__, self).__delattr__(name)'),
locals=locals,
overwrite_error=True)
overwrite_error='Consider using functools.total_ordering')
if frozen:
- _frozen_get_del_attr(cls, field_list, func_builder)
+ _frozen_set_del_attr(cls, field_list, func_builder)
# Decide if/how we're going to create a hash function.
hash_action = _hash_action[bool(unsafe_hash),
class TestFrozen(unittest.TestCase):
+ # Some tests have a subtest with a slotted dataclass.
+ # See https://github.com/python/cpython/issues/105936 for the reasons.
+
def test_frozen(self):
- @dataclass(frozen=True)
- class C:
- i: int
+ for slots in (False, True):
+ with self.subTest(slots=slots):
- c = C(10)
- self.assertEqual(c.i, 10)
- with self.assertRaises(FrozenInstanceError):
- c.i = 5
- self.assertEqual(c.i, 10)
+ @dataclass(frozen=True, slots=slots)
+ class C:
+ i: int
+
+ c = C(10)
+ self.assertEqual(c.i, 10)
+ with self.assertRaises(FrozenInstanceError):
+ c.i = 5
+ self.assertEqual(c.i, 10)
+ with self.assertRaises(FrozenInstanceError):
+ del c.i
+ self.assertEqual(c.i, 10)
def test_frozen_empty(self):
- @dataclass(frozen=True)
- class C:
- pass
+ for slots in (False, True):
+ with self.subTest(slots=slots):
- c = C()
- self.assertNotHasAttr(c, 'i')
- with self.assertRaises(FrozenInstanceError):
- c.i = 5
- self.assertNotHasAttr(c, 'i')
- with self.assertRaises(FrozenInstanceError):
- del c.i
+ @dataclass(frozen=True, slots=slots)
+ class C:
+ pass
+
+ c = C()
+ self.assertNotHasAttr(c, 'i')
+ with self.assertRaises(FrozenInstanceError):
+ c.i = 5
+ self.assertNotHasAttr(c, 'i')
+ with self.assertRaises(FrozenInstanceError):
+ del c.i
def test_inherit(self):
@dataclass(frozen=True)
d.i = 5
def test_non_frozen_normal_derived(self):
- # See bpo-32953.
-
- @dataclass(frozen=True)
- class D:
- x: int
- y: int = 10
-
- class S(D):
- pass
+ # See bpo-32953 and https://github.com/python/cpython/issues/105936
+ for slots in (False, True):
+ with self.subTest(slots=slots):
- s = S(3)
- self.assertEqual(s.x, 3)
- self.assertEqual(s.y, 10)
- s.cached = True
+ @dataclass(frozen=True, slots=slots)
+ class D:
+ x: int
+ y: int = 10
- # But can't change the frozen attributes.
- with self.assertRaises(FrozenInstanceError):
- s.x = 5
- with self.assertRaises(FrozenInstanceError):
- s.y = 5
- self.assertEqual(s.x, 3)
- self.assertEqual(s.y, 10)
- self.assertEqual(s.cached, True)
+ class S(D):
+ pass
- with self.assertRaises(FrozenInstanceError):
- del s.x
- self.assertEqual(s.x, 3)
- with self.assertRaises(FrozenInstanceError):
- del s.y
- self.assertEqual(s.y, 10)
- del s.cached
- self.assertNotHasAttr(s, 'cached')
- with self.assertRaises(AttributeError) as cm:
- del s.cached
- self.assertNotIsInstance(cm.exception, FrozenInstanceError)
+ s = S(3)
+ self.assertEqual(s.x, 3)
+ self.assertEqual(s.y, 10)
+ s.cached = True
+
+ # But can't change the frozen attributes.
+ with self.assertRaises(FrozenInstanceError):
+ s.x = 5
+ with self.assertRaises(FrozenInstanceError):
+ s.y = 5
+ self.assertEqual(s.x, 3)
+ self.assertEqual(s.y, 10)
+ self.assertEqual(s.cached, True)
+
+ with self.assertRaises(FrozenInstanceError):
+ del s.x
+ self.assertEqual(s.x, 3)
+ with self.assertRaises(FrozenInstanceError):
+ del s.y
+ self.assertEqual(s.y, 10)
+ del s.cached
+ self.assertNotHasAttr(s, 'cached')
+ with self.assertRaises(AttributeError) as cm:
+ del s.cached
+ self.assertNotIsInstance(cm.exception, FrozenInstanceError)
def test_non_frozen_normal_derived_from_empty_frozen(self):
@dataclass(frozen=True)
return SlotsTest
+ # See https://github.com/python/cpython/issues/135228#issuecomment-3755979059
+ def make_frozen():
+ @dataclass(frozen=True, slots=True)
+ class SlotsTest:
+ pass
+
+ return SlotsTest
+
def make_with_annotations():
@dataclass(slots=True)
class SlotsTest:
return SlotsTest
- for make in (make_simple, make_with_annotations, make_with_annotations_and_method, make_with_forwardref):
+ for make in (make_simple, make_frozen, make_with_annotations, make_with_annotations_and_method, make_with_forwardref):
with self.subTest(make=make):
C = make()
support.gc_collect()