Why does inspect fail to get source file for classes in a dynamically-imported module?

Why does inspect fail to get source file for classes in a dynamically-imported module?
python
Ethan Jackson

inspect.getsource() and inspect.getsourcefile() can access source info for a function, but not for a class, when they are in a module that is imported dynamically with importlib.

Here are two files, thing1.py and thing2.py:

  • thing1.py

    import inspect import os import importlib.util dir_here = os.path.dirname(__file__) spec = importlib.util.spec_from_file_location("thing2", os.path.join(dir_here, "thing2.py")) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) print(module.foo(3)) print(module.Bar().inc(3)) print("module source file:", inspect.getsourcefile(module)) for attr in ['foo','Bar']: print("%s source: %s" % (attr, inspect.getsourcefile(getattr(module, attr)))) print(inspect.getsource(getattr(module, attr)))
  • thing2.py

    def foo(x): return x+1 class Bar(object): def inc(self, x): return x+1

If I run test1.py here's what I get:

> python c:\tmp\python\test2\thing1.py 4 4 module source file: c:\tmp\python\test2\thing2.py foo source: c:\tmp\python\test2\thing2.py def foo(x): return x+1 Traceback (most recent call last): File "c:\tmp\python\test2\thing1.py", line 16, in <module> print("%s source: %s" % (attr, inspect.getsourcefile(getattr(module, attr)))) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\jason\.conda\envs\py3datalab\Lib\inspect.py", line 940, in getsourcefile filename = getfile(object) ^^^^^^^^^^^^^^^ File "C:\Users\jason\.conda\envs\py3datalab\Lib\inspect.py", line 909, in getfile raise TypeError('{!r} is a built-in class'.format(object)) TypeError: <class 'thing2.Bar'> is a built-in class

I'm using Python 3.11.4.

Am I missing something during my import step that tells Python how to get source info for classes?

Answer

The logic for getting the source file for a class looks like this:

if isclass(object): if hasattr(object, '__module__'): module = sys.modules.get(object.__module__) if getattr(module, '__file__', None): return module.__file__ if object.__module__ == '__main__': raise OSError('source code not available') raise TypeError('{!r} is a built-in class'.format(object))

In your case, module.Bar.__module__ is 'thing2', which has not been added sys.modules. Hence this machinery concludes (incorrectly) that it must be built-in, and raises an error claiming as much.

Am I missing something during my import step that tells Python how to get source info for classes?

Yes; note that the recipe in the importlib docs includes an explicit step to update sys.modules, as exec_module doesn't do it:

import importlib.util import sys def import_from_path(module_name, file_path): spec = importlib.util.spec_from_file_location(module_name, file_path) module = importlib.util.module_from_spec(spec)

Similarly adding sys.modules["thing2"] = module into thing1.py would allow it to show the class implementation.

Related Articles