Skip to content

Commit 517fc31

Browse files
committed
Improve performance of inspect.getmodule
getmodule maintains a cache of module names & associated file names. Whenever it encounters a module filename not in its cache, it iterates over sys.modules and repopulates its cache. An object which has no associated module filename will trigger this repopulation loop every time getmodule is called. The cost is easily seen with inspect.getstack def run_test(module): # cols have stack depths # rows have len(sys.modules) def print_line(h, *vals): if not h: print("%5s" % h, *["%6i" % v for v in vals]) else: print("%5s" % h, *["%6.1f" % v for v in vals]) def add_modules(n): import random import sys for _ in range(n): sys.modules[f"foo_{random.randint(0,2**64)}"] = module def measure(depth): import inspect import timeit def nest(level): if level > 0: return nest(level-1) else: dur = timeit.timeit(lambda: inspect.stack(), number=1) return dur * 1000 return nest(depth) import sys print_line("", *[2**e for e in range(7)]) for i in range(1, 5): add_modules(n=10**i) times = [measure(depth=2**e) for e in range(7)] print_line(len(sys.modules), *times) python 3.11.0a7+ optimized build ==== # sys has no __file__ and doesn't enter getmodule cache >>> import sys >>> run_test(sys) 1 2 4 8 16 32 64 87 2.1 0.8 1.0 1.3 2.0 3.3 6.1 187 2.0 2.5 2.7 3.6 5.5 9.5 16.8 1187 13.6 15.7 19.1 26.3 39.6 67.4 123.3 11187 134.1 150.3 183.4 252.0 383.5 750.3 1189.8 >>> exit() >>> import random >>> run_test(random) 1 2 4 8 16 32 64 87 2.1 0.7 0.9 1.2 1.7 2.9 5.6 187 2.3 1.1 1.5 1.8 2.5 4.3 7.8 1187 17.0 4.3 5.6 7.1 11.1 18.6 33.9 11187 164.6 37.4 45.7 61.6 94.7 161.0 297.7 >>> exit() python 3.11.0a7+ optimized build, with changes ==== >>> import sys >>> run_test(sys) 1 2 4 8 16 32 64 87 1.7 0.2 0.2 0.3 0.4 0.6 1.2 187 0.2 0.2 0.2 0.2 0.3 0.5 0.9 1187 0.2 0.2 0.2 0.3 0.3 0.5 0.9 11187 0.3 0.3 0.3 0.3 0.5 0.8 1.3 >>> exit() >>> import random >>> run_test(random) 1 2 4 8 16 32 64 87 1.8 0.2 0.2 0.3 0.4 0.6 1.2 187 0.2 0.2 0.2 0.3 0.3 0.6 0.9 1187 0.2 0.2 0.2 0.3 0.4 0.5 0.9 11187 0.3 0.2 0.2 0.3 0.3 0.6 1.0 >>> exit()
1 parent 3f61db4 commit 517fc31

1 file changed

Lines changed: 19 additions & 4 deletions

File tree

Lib/inspect.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -907,6 +907,14 @@ def getfile(object):
907907
if object.__module__ == '__main__':
908908
raise OSError('source code not available')
909909
raise TypeError('{!r} is a built-in class'.format(object))
910+
if code := _getcode(object):
911+
return code.co_filename
912+
raise TypeError('module, class, method, function, traceback, frame, or '
913+
'code object was expected, got {}'.format(
914+
type(object).__name__))
915+
916+
def _getcode(object):
917+
"""Get the code an object was defined in, if available."""
910918
if ismethod(object):
911919
object = object.__func__
912920
if isfunction(object):
@@ -916,10 +924,7 @@ def getfile(object):
916924
if isframe(object):
917925
object = object.f_code
918926
if iscode(object):
919-
return object.co_filename
920-
raise TypeError('module, class, method, function, traceback, frame, or '
921-
'code object was expected, got {}'.format(
922-
type(object).__name__))
927+
return object
923928

924929
def getmodulename(path):
925930
"""Return the module name for a given file, or None."""
@@ -969,20 +974,27 @@ def getabsfile(object, _filename=None):
969974

970975
modulesbyfile = {}
971976
_filesbymodname = {}
977+
_moduleless = set()
972978

973979
def getmodule(object, _filename=None):
974980
"""Return the module an object was defined in, or None if not found."""
975981
if ismodule(object):
976982
return object
977983
if hasattr(object, '__module__'):
978984
return sys.modules.get(object.__module__)
985+
code = _getcode(object)
986+
if code and hash(code) in _moduleless:
987+
return None
979988
# Try the filename to modulename cache
980989
if _filename is not None and _filename in modulesbyfile:
981990
return sys.modules.get(modulesbyfile[_filename])
982991
# Try the cache again with the absolute file name
983992
try:
984993
file = getabsfile(object, _filename)
985994
except (TypeError, FileNotFoundError):
995+
_moduleless.add(hash(code))
996+
return None
997+
if code and hash(code) in _moduleless:
986998
return None
987999
if file in modulesbyfile:
9881000
return sys.modules.get(modulesbyfile[file])
@@ -1004,6 +1016,7 @@ def getmodule(object, _filename=None):
10041016
# Check the main module
10051017
main = sys.modules['__main__']
10061018
if not hasattr(object, '__name__'):
1019+
_moduleless.add(hash(code))
10071020
return None
10081021
if hasattr(main, object.__name__):
10091022
mainobject = getattr(main, object.__name__)
@@ -1015,6 +1028,8 @@ def getmodule(object, _filename=None):
10151028
builtinobject = getattr(builtin, object.__name__)
10161029
if builtinobject is object:
10171030
return builtin
1031+
_moduleless.add(hash(code))
1032+
return None
10181033

10191034

10201035
class ClassFoundException(Exception):

0 commit comments

Comments
 (0)