Skip to content

Commit 1b19bd1

Browse files
AlexWaygoodcarljm
andauthored
gh-103193: cache calls to inspect._shadowed_dict in inspect.getattr_static (#104267)
Co-authored-by: Carl Meyer <[email protected]>
1 parent 60f5884 commit 1b19bd1

File tree

3 files changed

+32
-5
lines changed

3 files changed

+32
-5
lines changed

Doc/whatsnew/3.12.rst

+4-3
Original file line numberDiff line numberDiff line change
@@ -342,8 +342,9 @@ inspect
342342
(Contributed by Thomas Krennwallner in :issue:`35759`.)
343343

344344
* The performance of :func:`inspect.getattr_static` has been considerably
345-
improved. Most calls to the function should be around 2x faster than they
346-
were in Python 3.11. (Contributed by Alex Waygood in :gh:`103193`.)
345+
improved. Most calls to the function should be at least 2x faster than they
346+
were in Python 3.11, and some may be 6x faster or more. (Contributed by Alex
347+
Waygood in :gh:`103193`.)
347348

348349
pathlib
349350
-------
@@ -597,7 +598,7 @@ typing
597598
:func:`runtime-checkable protocols <typing.runtime_checkable>` has changed
598599
significantly. Most ``isinstance()`` checks against protocols with only a few
599600
members should be at least 2x faster than in 3.11, and some may be 20x
600-
faster or more. However, ``isinstance()`` checks against protocols with seven
601+
faster or more. However, ``isinstance()`` checks against protocols with fourteen
601602
or more members may be slower than in Python 3.11. (Contributed by Alex
602603
Waygood in :gh:`74690` and :gh:`103193`.)
603604

Lib/inspect.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -1794,8 +1794,9 @@ def _check_class(klass, attr):
17941794
return entry.__dict__[attr]
17951795
return _sentinel
17961796

1797-
def _shadowed_dict(klass):
1798-
for entry in _static_getmro(klass):
1797+
@functools.lru_cache()
1798+
def _shadowed_dict_from_mro_tuple(mro):
1799+
for entry in mro:
17991800
dunder_dict = _get_dunder_dict_of_class(entry)
18001801
if '__dict__' in dunder_dict:
18011802
class_dict = dunder_dict['__dict__']
@@ -1805,6 +1806,9 @@ def _shadowed_dict(klass):
18051806
return class_dict
18061807
return _sentinel
18071808

1809+
def _shadowed_dict(klass):
1810+
return _shadowed_dict_from_mro_tuple(_static_getmro(klass))
1811+
18081812
def getattr_static(obj, attr, default=_sentinel):
18091813
"""Retrieve attributes without triggering dynamic lookup via the
18101814
descriptor protocol, __getattr__ or __getattribute__.

Lib/test/test_inspect.py

+22
Original file line numberDiff line numberDiff line change
@@ -2111,6 +2111,28 @@ def __dict__(self):
21112111
self.assertEqual(inspect.getattr_static(foo, 'a'), 3)
21122112
self.assertFalse(test.called)
21132113

2114+
def test_mutated_mro(self):
2115+
test = self
2116+
test.called = False
2117+
2118+
class Foo(dict):
2119+
a = 3
2120+
@property
2121+
def __dict__(self):
2122+
test.called = True
2123+
return {}
2124+
2125+
class Bar(dict):
2126+
a = 4
2127+
2128+
class Baz(Bar): pass
2129+
2130+
baz = Baz()
2131+
self.assertEqual(inspect.getattr_static(baz, 'a'), 4)
2132+
Baz.__bases__ = (Foo,)
2133+
self.assertEqual(inspect.getattr_static(baz, 'a'), 3)
2134+
self.assertFalse(test.called)
2135+
21142136
def test_custom_object_dict(self):
21152137
test = self
21162138
test.called = False

0 commit comments

Comments
 (0)