mirror of
				https://github.com/python/cpython.git
				synced 2025-11-03 23:21:29 +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):
 | 
			
		||||
    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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2767,6 +2767,19 @@ class C:
 | 
			
		|||
            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 S(D):
 | 
			
		|||
        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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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