]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-102578: Optimise setting and deleting mutable attributes on non-dataclass subclass...
authorXuehai Pan <XuehaiPan@outlook.com>
Sat, 11 Mar 2023 00:21:22 +0000 (08:21 +0800)
committerGitHub <noreply@github.com>
Sat, 11 Mar 2023 00:21:22 +0000 (19:21 -0500)
Lib/dataclasses.py
Lib/test/test_dataclasses.py
Misc/NEWS.d/next/Library/2023-03-10-13-21-16.gh-issue-102578.-gujoI.rst [new file with mode: 0644]

index 8bc8594d674bc0d81cc2bd63ef85a8715b97cbc9..78a126f051e2e76e584b4eead4eef9d682e3db05 100644 (file)
@@ -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,
index 589f229f4623594fa80ab3cadf33eba92f85ee2a..5486b2ef3f47e5183ebc521c6cd888097edb0d3d 100644 (file)
@@ -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,
diff --git a/Misc/NEWS.d/next/Library/2023-03-10-13-21-16.gh-issue-102578.-gujoI.rst b/Misc/NEWS.d/next/Library/2023-03-10-13-21-16.gh-issue-102578.-gujoI.rst
new file mode 100644 (file)
index 0000000..7307148
--- /dev/null
@@ -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)``.