Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions Lib/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,35 @@ def getmodule(object, _filename=None):
return object
if hasattr(object, '__module__'):
return sys.modules.get(object.__module__)
if istraceback(object):
object = object.tb_frame
if isframe(object):
# Frame globals identify the execution namespace directly. Preserve
# the private filename override when it names a different file.
if _filename is None or _filename == object.f_code.co_filename:
object_globals = object.f_globals
module_name = object_globals.get('__name__')
if not isinstance(module_name, str):
return None
module = sys.modules.get(module_name)
if not (ismodule(module) and module.__dict__ is object_globals):
return None
module_file = getattr(module, '__file__', None)
if module_file is None:
return None
try:
file = getabsfile(object, _filename)
except (TypeError, FileNotFoundError):
return None
if object.f_code.co_filename == module_file:
return module
try:
module_file = getabsfile(module)
except (TypeError, FileNotFoundError):
return None
if file == module_file or file == os.path.realpath(module_file):
return module
return None

# Try the filename to modulename cache
if _filename is not None and _filename in modulesbyfile:
Expand Down
79 changes: 79 additions & 0 deletions Lib/test/test_inspect/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,85 @@ def test_getmodule(self):
self.assertEqual(inspect.getmodule(str), sys.modules["builtins"])
# Check filename override
self.assertEqual(inspect.getmodule(None, modfile), mod)
self.assertEqual(
inspect.getmodule(inspect.currentframe(), modfile), mod)
# Check code, frame, and traceback objects
self.assertIs(inspect.getmodule(mod.eggs.__code__), mod)
self.assertIs(inspect.getmodule(inspect.currentframe()),
sys.modules[__name__])
try:
1 / 0
except ZeroDivisionError as error:
self.assertIs(inspect.getmodule(error.__traceback__),
sys.modules[__name__])

def test_getmodule_unregistered_exec_frame(self):
def exec_namespace(namespace):
exec(compile(textwrap.dedent("""
frame = inspect.currentframe()
try:
1 / 0
except ZeroDivisionError as error:
traceback = error.__traceback__
"""), modfile, "exec"), namespace)
self.assertIsNone(inspect.getmodule(namespace["frame"]))
self.assertIsNone(inspect.getmodule(namespace["traceback"]))

# Missing and invalid module names identify no registered namespace.
exec_namespace({"inspect": inspect})
exec_namespace({"inspect": inspect, "__name__": []})

module_name = f"{__name__}.not_registered"
for module in (None, object(), types.ModuleType(module_name)):
with self.subTest(module=module):
sys.modules[module_name] = module
try:
exec_namespace({
"inspect": inspect,
"__name__": module_name,
})
finally:
del sys.modules[module_name]

def test_getmodule_registered_exec_frame(self):
def exec_module(module, filename):
module.inspect = inspect
exec(compile(textwrap.dedent("""
frame = inspect.currentframe()
try:
1 / 0
except ZeroDivisionError as error:
traceback = error.__traceback__
"""), filename, "exec"), module.__dict__)

module_name = f"{__name__}.registered"
module = types.ModuleType(module_name)
sys.modules[module_name] = module
try:
with temp_cwd() as cwd:
filename = os.path.join(cwd, "registered.py")
with open(filename, "w"):
pass
module.__file__ = filename

exec_module(module, filename)
self.assertIs(inspect.getmodule(module.frame), module)
self.assertIs(inspect.getmodule(module.traceback), module)

# Globals identity is insufficient when the code came from a
# different origin than the registered module.
exec_module(module, filename + ".other")
self.assertIsNone(inspect.getmodule(module.frame))
self.assertIsNone(inspect.getmodule(module.traceback))

# Preserve the existing result for fileless modules while
# avoiding a scan of sys.modules.
del module.__file__
exec_module(module, "<fileless>")
self.assertIsNone(inspect.getmodule(module.frame))
self.assertIsNone(inspect.getmodule(module.traceback))
finally:
del sys.modules[module_name]

def test_getmodule_file_not_found(self):
# See bpo-45406
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improve :func:`inspect.getmodule` performance for frame and traceback objects by resolving registered modules directly from frame globals. Frames executing in unregistered globals now return ``None`` instead of being associated with a module solely by filename.
Loading