pythonpython-importlibpython-inspect

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


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:

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?


Solution

  • 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 to 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)
        sys.modules[module_name] = module
        spec.loader.exec_module(module)
        return module
    

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