132

I'm having trouble understanding __file__. From what I understand, __file__ returns the absolute path from which the module was loaded.

I'm having problem producing this: I have a abc.py with one statement print __file__, running from /d/projects/ python abc.py returns abc.py. running from /d/ returns projects/abc.py. Any reasons why?

2

5 Answers 5

108

__file__ is guaranteed to be an absolute path in Python 3.9+.

In Python 3.4 (changelog)

Module __file__ attributes (and related values) should now always contain absolute paths by default, with the sole exception of __main__.__file__ when a script has been executed directly using a relative path.

In Python 3.9 (changelog):

... the __file__ attribute of the __main__ module became an absolute path


From the documentation:

The pathname of the file from which the module was loaded, if it was loaded from a file. The __file__ attribute may be missing for certain types of modules, such as C modules that are statically linked into the interpreter. For extension modules loaded dynamically from a shared library, it's the pathname of the shared library file.

From the mailing list thread linked by @kindall in a comment to the question:

I haven't tried to repro this particular example, but the reason is that we don't want to have to call getpwd() on every import nor do we want to have some kind of in-process variable to cache the current directory. (getpwd() is relatively slow and can sometimes fail outright, and trying to cache it has a certain risk of being wrong.)

What we do instead, is code in site.py that walks over the elements of sys.path and turns them into absolute paths. However this code runs before '' is inserted in the front of sys.path, so that the initial value of sys.path is ''.

For the rest of this, consider sys.path not to include ''.

So, if you are outside the part of sys.path that contains the module, you'll get an absolute path. If you are inside the part of sys.path that contains the module, you'll get a relative path.

If you load a module in the current directory, and the current directory isn't in sys.path, you'll get an absolute path.

If you load a module in the current directory, and the current directory is in sys.path, you'll get a relative path.

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

4 Comments

so does it means that if there is a path from '' to the module, a relative path would be used, if not an absolute path would be used since the remainder of sys.path are absolute..
If you load a module in the current directory, and the current directory isn't in sys.path, you'll get an absolute path. If you load a module in the current directory, and the current directory is in sys.path, you'll get a relative path.
Remember, for this purpose, sys.path doesn't include ''.
got it, but @agf, if i use python /foo/abc.py from /home, I suppose the part of sys.path that contains the module is /home/foo and my current directory is /home/, why does print file gives me a relative path?
63

__file__ is absolute since Python 3.4, except when executing a script directly using a relative path:

Module __file__ attributes (and related values) should now always contain absolute paths by default, with the sole exception of __main__.__file__ when a script has been executed directly using a relative path. (Contributed by Brett Cannon in bpo-18416.)

Not sure if it resolves symlinks though.

Example of passing a relative path:

$ python script.py

3 Comments

This is not true for Python 3.4.0 (Python 3.4.0 (default, Apr 11 2014, 13:05:11) [GCC 4.8.2] on linux). And symlinks are not resolved in my trials.
@FrozenFlame, feel free to report to bugs.python.org if 3.4.1 doesn't fix it.
It is now always an absolute path since Python 3.9 docs.python.org/3/whatsnew/3.9.html#other-language-changes
21

Late simple example:

from os import path, getcwd, chdir

def print_my_path():
    print('cwd:     {}'.format(getcwd()))
    print('__file__:{}'.format(__file__))
    print('abspath: {}'.format(path.abspath(__file__)))

print_my_path()

chdir('..')

print_my_path()

Under Python-2.*, the second call incorrectly determines the path.abspath(__file__) based on the current directory:

cwd:     C:\codes\py
__file__:cwd_mayhem.py
abspath: C:\codes\py\cwd_mayhem.py
cwd:     C:\codes
__file__:cwd_mayhem.py
abspath: C:\codes\cwd_mayhem.py

As noted by @techtonik, in Python 3.4+, this will work fine since __file__ returns an absolute path.

2 Comments

... except for the __main__ module, where __file__ may be a relative path.
even in __main__ it will be absolute in 3.9+ now, that was mentioned in changelog
8

__file__ can be relative or absolute depending on the python version used and whether the module is executed directly or not.

TL;DR:

  • From python 3.5 to 3.8, __file__ is set to the relative path of the module w.r.t. the current working directory if the module is called directly. Otherwise it is set to the absolute path.
  • Since python 3.9, __file__ is set to the absolute path even if the corresponding module is executed directly.

This behavior is explained in What's new In Python 3.9 (thanks to @user0's comment):

Python now gets the absolute path of the script filename specified on the command line (ex: python3 script.py): the __file__ attribute of the __main__ module became an absolute path, rather than a relative path. These paths now remain valid after the current directory is changed by os.chdir(). As a side effect, the traceback also displays the absolute path for __main__ module frames in this case.

Toy example: For instance, having the following setup (inspired by this answer):

# x.py:
from pathlib import Path
import y
print(__file__)
print(Path(__file__))
print(Path(__file__).resolve())
# y.py:
from pathlib import Path
print(__file__)
print(Path(__file__))

with x.py and y.py in the same directory. The different outputs after going to this directory and executing:

python x.py

are:

Python 3.5 - 3.8:

D:\py_tests\y.py
D:\py_tests\y.py
x.py
x.py
D:\py_tests\x.py

Python 3.9 - 3.11

D:\py_tests\y.py
D:\py_tests\y.py
D:\py_tests\x.py
D:\py_tests\x.py
D:\py_tests\x.py

6 Comments

So starting from Python 3.9, __file__ is always an absolute path and can never be a relative path?
@user0 Yes, as of today (3.11 is the latest Python version) , __file__ is always an absolute path.
Is there anything in the official documentation for Python that says so? The closest thing I can find is this.
@user0 Your link is a great reference. The consequence that it also explains is that, before Python 3.9, if you changed the working directory (the directory from which the __main__ module is called --D:\py_tests in my example above) using os.chdir(), the __file__ attribute of the __main__ module would be invalid. This is because __file__ stored a constant relative path, unaffected by changes produced by os.chdir(). After Python 3.9, since __file__ constains an absolute path, its value remains valid after changes produced by os.chdir().
What does this add over marcz's answer from 2014?
|
6

With the help of the of Guido mail provided by @kindall, we can understand the standard import process as trying to find the module in each member of sys.path, and file as the result of this lookup (more details in PyMOTW Modules and Imports.). So if the module is located in an absolute path in sys.path the result is absolute, but if it is located in a relative path in sys.path the result is relative.

Now the site.py startup file takes care of delivering only absolute path in sys.path, except the initial '', so if you don't change it by other means than setting the PYTHONPATH (whose path are also made absolute, before prefixing sys.path), you will get always an absolute path, but when the module is accessed through the current directory.

Now if you trick sys.path in a funny way you can get anything.

As example if you have a sample module foo.py in /tmp/ with the code:

import sys
print(sys.path)
print (__file__)

If you go in /tmp you get:

>>> import foo
['', '/tmp', '/usr/lib/python3.3', ...]
./foo.py

When in in /home/user, if you add /tmp your PYTHONPATH you get:

>>> import foo
['', '/tmp', '/usr/lib/python3.3', ...]
/tmp/foo.py

Even if you add ../../tmp, it will be normalized and the result is the same.

But if instead of using PYTHONPATH you use directly some funny path you get a result as funny as the cause.

>>> import sys
>>> sys.path.append('../../tmp')
>>> import foo
['', '/usr/lib/python3.3', .... , '../../tmp']
../../tmp/foo.py

Guido explains in the above cited thread, why python do not try to transform all entries in absolute paths:

we don't want to have to call getpwd() on every import .... getpwd() is relatively slow and can sometimes fail outright,

So your path is used as it is.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.