mirror of
https://github.com/python/cpython.git
synced 2025-08-22 17:55:18 +00:00
gh-102578: Optimise setting and deleting mutable attributes on non-dataclass subclasses of frozen dataclasses (gh-102573)
This commit is contained in:
parent
90f1d77717
commit
ee6f8413a9
3 changed files with 52 additions and 6 deletions
|
@ -616,21 +616,19 @@ def _repr_fn(fields, globals):
|
||||||
def _frozen_get_del_attr(cls, fields, globals):
|
def _frozen_get_del_attr(cls, fields, globals):
|
||||||
locals = {'cls': cls,
|
locals = {'cls': cls,
|
||||||
'FrozenInstanceError': FrozenInstanceError}
|
'FrozenInstanceError': FrozenInstanceError}
|
||||||
|
condition = 'type(self) is cls'
|
||||||
if fields:
|
if fields:
|
||||||
fields_str = '(' + ','.join(repr(f.name) for f in fields) + ',)'
|
condition += ' or name in {' + ', '.join(repr(f.name) for f in fields) + '}'
|
||||||
else:
|
|
||||||
# Special case for the zero-length tuple.
|
|
||||||
fields_str = '()'
|
|
||||||
return (_create_fn('__setattr__',
|
return (_create_fn('__setattr__',
|
||||||
('self', 'name', 'value'),
|
('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}")',
|
' raise FrozenInstanceError(f"cannot assign to field {name!r}")',
|
||||||
f'super(cls, self).__setattr__(name, value)'),
|
f'super(cls, self).__setattr__(name, value)'),
|
||||||
locals=locals,
|
locals=locals,
|
||||||
globals=globals),
|
globals=globals),
|
||||||
_create_fn('__delattr__',
|
_create_fn('__delattr__',
|
||||||
('self', 'name'),
|
('self', 'name'),
|
||||||
(f'if type(self) is cls or name in {fields_str}:',
|
(f'if {condition}:',
|
||||||
' raise FrozenInstanceError(f"cannot delete field {name!r}")',
|
' raise FrozenInstanceError(f"cannot delete field {name!r}")',
|
||||||
f'super(cls, self).__delattr__(name)'),
|
f'super(cls, self).__delattr__(name)'),
|
||||||
locals=locals,
|
locals=locals,
|
||||||
|
|
|
@ -2767,6 +2767,19 @@ class TestFrozen(unittest.TestCase):
|
||||||
c.i = 5
|
c.i = 5
|
||||||
self.assertEqual(c.i, 10)
|
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):
|
def test_inherit(self):
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class C:
|
class C:
|
||||||
|
@ -2890,6 +2903,37 @@ class TestFrozen(unittest.TestCase):
|
||||||
self.assertEqual(s.y, 10)
|
self.assertEqual(s.y, 10)
|
||||||
self.assertEqual(s.cached, True)
|
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):
|
def test_overwriting_frozen(self):
|
||||||
# frozen uses __setattr__ and __delattr__.
|
# frozen uses __setattr__ and __delattr__.
|
||||||
with self.assertRaisesRegex(TypeError,
|
with self.assertRaisesRegex(TypeError,
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
Speed up setting or deleting mutable attributes on non-dataclass subclasses of
|
||||||
|
frozen dataclasses. Due to the implementation of ``__setattr__`` and
|
||||||
|
``__delattr__`` for frozen dataclasses, this previously had a time complexity
|
||||||
|
of ``O(n)``. It now has a time complexity of ``O(1)``.
|
Loading…
Add table
Add a link
Reference in a new issue