gh-103193: cache calls to `inspect._shadowed_dict` in `inspect.getattr_static` (#104267)

Co-authored-by: Carl Meyer <carl@oddbird.net>
This commit is contained in:
Alex Waygood 2023-05-07 18:45:09 +01:00 committed by GitHub
parent 60f588478f
commit 1b19bd1a88
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 32 additions and 5 deletions

View File

@ -342,8 +342,9 @@ inspect
(Contributed by Thomas Krennwallner in :issue:`35759`.) (Contributed by Thomas Krennwallner in :issue:`35759`.)
* The performance of :func:`inspect.getattr_static` has been considerably * The performance of :func:`inspect.getattr_static` has been considerably
improved. Most calls to the function should be around 2x faster than they improved. Most calls to the function should be at least 2x faster than they
were in Python 3.11. (Contributed by Alex Waygood in :gh:`103193`.) were in Python 3.11, and some may be 6x faster or more. (Contributed by Alex
Waygood in :gh:`103193`.)
pathlib pathlib
------- -------
@ -597,7 +598,7 @@ typing
:func:`runtime-checkable protocols <typing.runtime_checkable>` has changed :func:`runtime-checkable protocols <typing.runtime_checkable>` has changed
significantly. Most ``isinstance()`` checks against protocols with only a few significantly. Most ``isinstance()`` checks against protocols with only a few
members should be at least 2x faster than in 3.11, and some may be 20x members should be at least 2x faster than in 3.11, and some may be 20x
faster or more. However, ``isinstance()`` checks against protocols with seven faster or more. However, ``isinstance()`` checks against protocols with fourteen
or more members may be slower than in Python 3.11. (Contributed by Alex or more members may be slower than in Python 3.11. (Contributed by Alex
Waygood in :gh:`74690` and :gh:`103193`.) Waygood in :gh:`74690` and :gh:`103193`.)

View File

@ -1794,8 +1794,9 @@ def _check_class(klass, attr):
return entry.__dict__[attr] return entry.__dict__[attr]
return _sentinel return _sentinel
def _shadowed_dict(klass): @functools.lru_cache()
for entry in _static_getmro(klass): def _shadowed_dict_from_mro_tuple(mro):
for entry in mro:
dunder_dict = _get_dunder_dict_of_class(entry) dunder_dict = _get_dunder_dict_of_class(entry)
if '__dict__' in dunder_dict: if '__dict__' in dunder_dict:
class_dict = dunder_dict['__dict__'] class_dict = dunder_dict['__dict__']
@ -1805,6 +1806,9 @@ def _shadowed_dict(klass):
return class_dict return class_dict
return _sentinel return _sentinel
def _shadowed_dict(klass):
return _shadowed_dict_from_mro_tuple(_static_getmro(klass))
def getattr_static(obj, attr, default=_sentinel): def getattr_static(obj, attr, default=_sentinel):
"""Retrieve attributes without triggering dynamic lookup via the """Retrieve attributes without triggering dynamic lookup via the
descriptor protocol, __getattr__ or __getattribute__. descriptor protocol, __getattr__ or __getattribute__.

View File

@ -2111,6 +2111,28 @@ def __dict__(self):
self.assertEqual(inspect.getattr_static(foo, 'a'), 3) self.assertEqual(inspect.getattr_static(foo, 'a'), 3)
self.assertFalse(test.called) self.assertFalse(test.called)
def test_mutated_mro(self):
test = self
test.called = False
class Foo(dict):
a = 3
@property
def __dict__(self):
test.called = True
return {}
class Bar(dict):
a = 4
class Baz(Bar): pass
baz = Baz()
self.assertEqual(inspect.getattr_static(baz, 'a'), 4)
Baz.__bases__ = (Foo,)
self.assertEqual(inspect.getattr_static(baz, 'a'), 3)
self.assertFalse(test.called)
def test_custom_object_dict(self): def test_custom_object_dict(self):
test = self test = self
test.called = False test.called = False