diff --git a/Lib/inspect.py b/Lib/inspect.py index 0b498b5c878..ba2021ab949 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -403,6 +403,7 @@ def getabsfile(object, _filename=None): return os.path.normcase(os.path.abspath(_filename)) modulesbyfile = {} +_filesbymodname = {} def getmodule(object, _filename=None): """Return the module an object was defined in, or None if not found.""" @@ -410,19 +411,32 @@ def getmodule(object, _filename=None): return object if hasattr(object, '__module__'): return sys.modules.get(object.__module__) + # Try the filename to modulename cache + if _filename is not None and _filename in modulesbyfile: + return sys.modules.get(modulesbyfile[_filename]) + # Try the cache again with the absolute file name try: file = getabsfile(object, _filename) except TypeError: return None if file in modulesbyfile: return sys.modules.get(modulesbyfile[file]) - for module in sys.modules.values(): + # Update the filename to module name cache and check yet again + # Copy sys.modules in order to cope with changes while iterating + for modname, module in sys.modules.items(): if ismodule(module) and hasattr(module, '__file__'): + f = module.__file__ + if f == _filesbymodname.get(modname, None): + # Have already mapped this module, so skip it + continue + _filesbymodname[modname] = f f = getabsfile(module) + # Always map to the name the module knows itself by modulesbyfile[f] = modulesbyfile[ os.path.realpath(f)] = module.__name__ if file in modulesbyfile: return sys.modules.get(modulesbyfile[file]) + # Check the main module main = sys.modules['__main__'] if not hasattr(object, '__name__'): return None @@ -430,6 +444,7 @@ def getmodule(object, _filename=None): mainobject = getattr(main, object.__name__) if mainobject is object: return main + # Check builtins builtin = sys.modules['__builtin__'] if hasattr(builtin, object.__name__): builtinobject = getattr(builtin, object.__name__) @@ -444,7 +459,7 @@ def findsource(object): in the file and the line number indexes a line in that list. An IOError is raised if the source code cannot be retrieved.""" file = getsourcefile(object) or getfile(object) - module = getmodule(object) + module = getmodule(object, file) if module: lines = linecache.getlines(file, module.__dict__) else: diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py index fa4bd4003f2..d880e87dcdf 100644 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -178,7 +178,18 @@ class TestRetrievingSourceCode(GetSourceBase): self.assertEqual(inspect.getcomments(mod.StupidGit), '# line 20\n') def test_getmodule(self): + # Check actual module + self.assertEqual(inspect.getmodule(mod), mod) + # Check class (uses __module__ attribute) self.assertEqual(inspect.getmodule(mod.StupidGit), mod) + # Check a method (no __module__ attribute, falls back to filename) + self.assertEqual(inspect.getmodule(mod.StupidGit.abuse), mod) + # Do it again (check the caching isn't broken) + self.assertEqual(inspect.getmodule(mod.StupidGit.abuse), mod) + # Check a builtin + self.assertEqual(inspect.getmodule(str), sys.modules["__builtin__"]) + # Check filename override + self.assertEqual(inspect.getmodule(None, modfile), mod) def test_getsource(self): self.assertSourceEqual(git.abuse, 29, 39)