How do you properly determine the current script directory?

Viewed 161648

I would like to see what is the best way to determine the current script directory in Python.

I discovered that, due to the many ways of calling Python code, it is hard to find a good solution.

Here are some problems:

  • __file__ is not defined if the script is executed with exec, execfile
  • __module__ is defined only in modules

Use cases:

  • ./myfile.py
  • python myfile.py
  • ./somedir/myfile.py
  • python somedir/myfile.py
  • execfile('myfile.py') (from another script, that can be located in another directory and that can have another current directory.

I know that there is no perfect solution, but I'm looking for the best approach that solves most of the cases.

The most used approach is os.path.dirname(os.path.abspath(__file__)) but this really doesn't work if you execute the script from another one with exec().

Warning

Any solution that uses current directory will fail, this can be different based on the way the script is called or it can be changed inside the running script.

16 Answers

Note: this answer is now a package

https://github.com/heetbeet/locate

$ pip install locate

$ python
>>> from locate import this_dir
>>> print(this_dir())
C:/Users/simon

For .py scripts as well as interactive usage:

I frequently use the directory of my scripts (for accessing files stored along side them), but I also frequently run these scripts in an interactive shell for debugging purposes. I define this_dir as:

  • When running or importing a .py file, the file's base directory. This is always the correct path.
  • When running an .ipyn notebook, the current working directory. This is always the correct path, since Jupyter sets the working directory as the .ipynb base directory.
  • When running in a REPL, the current working directory. Hmm, what is the actual "correct path" when the code is detached from a file? Rather, make it your responsibility to change into the "correct path" before invoking the REPL.

Python 3.4 (and above):

from pathlib import Path
this_dir = Path(globals().get("__file__", "./_")).absolute().parent

Python 2 (and above):

import os
this_dir = os.path.dirname(os.path.abspath(globals().get("__file__", "./_")))

Explanation:

  • globals() returns all the global variables as a dictionary.
  • .get("__file__", "./_") returns the value from the key "__file__" if it exists in globals(), otherwise it returns the provided default value "./_".
  • The rest of the code just expands __file__ (or "./_") into an absolute filepath, and then returns the filepath's base directory.

If __file__ is available:

# -- script1.py --
import os
file_path = os.path.abspath(__file__)
print(os.path.dirname(file_path))

For those we want to be able to run the command from the interpreter or get the path of the place you're running the script from:

# -- script2.py --
import os
print(os.path.abspath(''))

This works from the interpreter. But when run in a script (or imported) it gives the path of the place where you ran the script from, not the path of directory containing the script with the print.

Example:

If your directory structure is

test_dir (in the home dir)
├── main.py
└── test_subdir
    ├── script1.py
    └── script2.py

with

# -- main.py --
import script1.py
import script2.py

The output is:

~/test_dir/test_subdir
~/test_dir

To get the absolute path to the directory containing the current script you can use:

from pathlib import Path
absDir = Path(__file__).parent.resolve()

Please note the .resolve() call is required, because that is the one making the path absolute. Without resolve(), you would obtain something like '.'.

This solution uses pathlib, which is part of Python's stdlib since v3.4 (2014). This is preferrable compared to other solutions using os.

The official pathlib documentation has a useful table mapping the old os functions to the new ones: https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module

As previous answers require you to import some module, I thought that I would write one answer that doesn't. Use the code below if you don't want to import anything.

dir = '/'.join(__file__.split('/')[:-1])
print(dir)

If the script is on /path/to/script.py then this would print /path/to. Note that this will throw error on terminal as no file is executed. This basically parse the directory from __file__ removing the last part of it. In this case /script.py is removed to produce the output /path/to.

print(__import__("pathlib").Path(__file__).parent)
Related