How to get the file name where the function is defined?

Hi all,

Suppose there is a module:

# mylib.py
from functools import wraps

def dcor(f):
    @wraps(f)
    def _f(*args, **kwargs):
        return f(*args, **kwargs)
    return _f

and a script file in the same directory:

# test_script.py
import inspect
from mylib import dcor

def f(): pass

@dcor
def g(): pass

print(inspect.getfile(f))
print(inspect.getfile(g))

I expected both prints to output “…/test_script.py” because the second one is equivalent to g = dcor(g). So the first one does, but the second one prints “…/mylib.py”.
What I want to do is to get the “correct” filename from g.
Is it possible?
Thank you in advance!

The function g is actually the function _f from inside dcor, so it’s quite correct to say that that comes from mylib. But if you want to inspect the wrapped function, look at g.__wrapped__, which functools.wraps() adds as an attribute.

1 Like

Thank you very much for your quick help!

>>> print(inspect.getfile(g.__wrapped__))
...(snip)...\test_script.py

Thank you!

No probs!

You’ll find a number of functions (such as help()) will look for __wrapped__, and if it exists, use that instead of the original function.

Yes, and I also looked into the inspect module.
I found a nice method inspect.unwrap that recursively peels the wrappers, so I’ll use that.

Import modules get a __file__ attribute, which of course applies to everything defined within it.

>>> import tkinter
>>> tkinter.__file__
'C:\\Programs\\Python312\\Lib\\tkinter\\__init__.py'

I suspect that inspect gets the module from the function and thence the file.

Hi Terry,

The inspect module seems to use f.__code__.co_filename.
Using __import__(g.__module__).__file__ looked cool … except one case I found:

# test_script.py
from mylib import dcor

@dcor
def g(): pass

if __name__ == "__main__":
    print(__import__(g.__module__))
    print(__import__(g.__module__).__file__)

If you launch this script,

>py -i test_script.py
<module '__main__' from 'C:\\usr\\home\\lib\\python\\test-py\\test_script.py'>
C:\usr\home\lib\python\test-py\test_script.py
>>>

So far, so good. However, it fails when I continue with interactive input!

>>> __import__(g.__module__)
<module '__main__' (<_frozen_importlib_external.SourceFileLoader object at 0x00000208D3B08D60>)>
>>> __import__(g.__module__).__file__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module '__main__' has no attribute '__file__'. Did you mean: '__name__'?
>>>

I guess “frozen” has something to do with it…, but I don’t know why. :cold_face:

Not all imported modules have a __file__ attribute, because not all modules come from a file.

You must always be prepared to deal with built in modules which are part of the interpreter, and module objects that were created dynamically.

>>> import sys
>>> sys.__file__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module 'sys' has no attribute '__file__'. Did you mean: '__name__'?

>>> from types import ModuleType
>>> mod = ModuleType('mymod', {})
>>> mod
<module 'mymod'>
>>> mod.__file__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module 'mymod' has no attribute '__file__'. Did you mean: '__name__'?

I’m not sure what happens to modules loaded from a zip file. Something exciting, probably.

“frozen” is, I think, an implementation detail of how modules are currently loaded. That may or may not exist in alternate Python interpreters, or the future.

More important is that you are running in the interactive interpreter.

Normally, in the interactive interpreter, the __main__ module object has no __file__ attribute because there is no file being executed!

>>> import __main__
>>> hasattr(__main__, "__file__")
False

Normally when you run a file as a script, the __main__ module is that script, and the __file__ attribute is set accordingly.

[steve ~]$ echo "import __main__; print(__main__.__file__)" > 
demo.py
[steve ~]$ python3.10 demo.py 
/home/steve/demo.py

But when you use -i, you get both behaviours! While the script is running, the __file__ is set to the actual file; then when the script is over and you enter the interative interpreter, the attribute is removed.

I can’t tell if that makes perfect sense or is the weirdest gotcha I’ve ever seen. I genuinely don’t know what to make of that.

2 Likes