0

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?

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

2 Comments

ah, thanks. I got partway towards the same conclusion but you beat me to it. Is there any reason inspect requires a module to be added to sys.modules? I realize I can make the module name anything I want (__gesundheit__.thing2 for example), to avoid collisions, but it seems funny to put this into the global sys.modules if I'm just inspecting objects from a module loaded dynamically by path.
It has to get back from the class to the module it came from somehow, and given that the only thing available on Bar is the name of the module, as a string, sys.modules is generally where you'd do that. I don't think inspect offers an API where you provide the module, and inspect.getmodule(module.Bar) is None. It's unfortunate that this is showing up as a "built-in class", though; even the OSError('source code not available') branch would be less confusing.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.