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?
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.