diff options
author | Xuehai Pan <XuehaiPan@outlook.com> | 2023-03-11 00:21:22 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-03-11 00:21:22 (GMT) |
commit | ee6f8413a99d0ee4828e1c81911e203d3fff85d5 (patch) | |
tree | 195fa05d38f44e19548fe5f5255b2c202c4d4e1c /Lib | |
parent | 90f1d777177e28b6c7b8d9ba751550e373d61b0a (diff) | |
download | cpython-ee6f8413a99d0ee4828e1c81911e203d3fff85d5.zip cpython-ee6f8413a99d0ee4828e1c81911e203d3fff85d5.tar.gz cpython-ee6f8413a99d0ee4828e1c81911e203d3fff85d5.tar.bz2 |
gh-102578: Optimise setting and deleting mutable attributes on non-dataclass subclasses of frozen dataclasses (gh-102573)
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/dataclasses.py | 10 | ||||
-rw-r--r-- | Lib/test/test_dataclasses.py | 44 |
2 files changed, 48 insertions, 6 deletions
diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index 8bc8594..78a126f 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -616,21 +616,19 @@ def _repr_fn(fields, globals): def _frozen_get_del_attr(cls, fields, globals): locals = {'cls': cls, 'FrozenInstanceError': FrozenInstanceError} + condition = 'type(self) is cls' if fields: - fields_str = '(' + ','.join(repr(f.name) for f in fields) + ',)' - else: - # Special case for the zero-length tuple. - fields_str = '()' + condition += ' or name in {' + ', '.join(repr(f.name) for f in fields) + '}' return (_create_fn('__setattr__', ('self', 'name', 'value'), - (f'if type(self) is cls or name in {fields_str}:', + (f'if {condition}:', ' raise FrozenInstanceError(f"cannot assign to field {name!r}")', f'super(cls, self).__setattr__(name, value)'), locals=locals, globals=globals), _create_fn('__delattr__', ('self', 'name'), - (f'if type(self) is cls or name in {fields_str}:', + (f'if {condition}:', ' raise FrozenInstanceError(f"cannot delete field {name!r}")', f'super(cls, self).__delattr__(name)'), locals=locals, diff --git a/Lib/test/test_dataclasses.py b/Lib/test/test_dataclasses.py index 589f229..5486b2e 100644 --- a/Lib/test/test_dataclasses.py +++ b/Lib/test/test_dataclasses.py @@ -2767,6 +2767,19 @@ class TestFrozen(unittest.TestCase): c.i = 5 self.assertEqual(c.i, 10) + def test_frozen_empty(self): + @dataclass(frozen=True) + class C: + pass + + c = C() + self.assertFalse(hasattr(c, 'i')) + with self.assertRaises(FrozenInstanceError): + c.i = 5 + self.assertFalse(hasattr(c, 'i')) + with self.assertRaises(FrozenInstanceError): + del c.i + def test_inherit(self): @dataclass(frozen=True) class C: @@ -2890,6 +2903,37 @@ class TestFrozen(unittest.TestCase): 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.assertFalse(hasattr(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) + class D: + pass + + class S(D): + pass + + s = S() + self.assertFalse(hasattr(s, 'x')) + s.x = 5 + self.assertEqual(s.x, 5) + + del s.x + self.assertFalse(hasattr(s, 'x')) + with self.assertRaises(AttributeError) as cm: + del s.x + self.assertNotIsInstance(cm.exception, FrozenInstanceError) + def test_overwriting_frozen(self): # frozen uses __setattr__ and __delattr__. with self.assertRaisesRegex(TypeError, |