402

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.

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
bogdan
  • 9,056
  • 10
  • 37
  • 42
  • 1
    Can you be more specific where you need to know where the file comes from? - in the code thats importing the file (include-aware host) or in the file thats imported? (self-aware slave) – synthesizerpatel Jun 01 '11 at 21:19
  • 5
    See Ron Kalian's `pathlib` solution if you're using python 3.4 or higher: https://stackoverflow.com/a/48931294/1011724 – Dan Aug 22 '18 at 11:46
  • So the solution is NOT to use any current directory in code, but to use some config file? – ZhaoGang Sep 13 '18 at 03:43
  • Interesting discovery, I just made: When doing `python myfile.py` from a shell, it works, but both `:!python %` and `:!python myfile.py` from within **vim** fail with _The system cannot find the path specified._ This is quite annoying. Can anyone comment on the reason behind this and potential workarounds? – inVader Nov 27 '18 at 16:34

16 Answers16

324
os.path.dirname(os.path.abspath(__file__))

is indeed the best you're going to get.

It's unusual to be executing a script with exec/execfile; normally you should be using the module infrastructure to load scripts. If you must use these methods, I suggest setting __file__ in the globals you pass to the script so it can read that filename.

There's no other way to get the filename in execed code: as you note, the CWD may be in a completely different place.

bobince
  • 528,062
  • 107
  • 651
  • 834
  • 6
    Never say never? According to this: https://stackoverflow.com/a/18489147 answer a cross-platform solution is abspath(getsourcefile(lambda:0))? Or is there something else I'm missing? – Jeff Ellen Jul 12 '18 at 06:04
  • 4
    Update with pathlib: pathlib.Path(__file__).resolve()/'..' – dominecf Jun 11 '21 at 11:19
  • 4
    `os.path.dirname(os.path.abspath(__file__))` absolutely is *not* the best you're going to get. That's not even trying, really. As @JeffEllen suggests, see [this](https://stackoverflow.com/a/18489147/2809027) and [this](https://stackoverflow.com/a/6209894/2809027) for substantially safer, more robust, and more portable alternatives. – Cecil Curry Sep 08 '21 at 06:33
151

If you really want to cover the case that a script is called via execfile(...), you can use the inspect module to deduce the filename (including the path). As far as I am aware, this will work for all cases you listed:

filename = inspect.getframeinfo(inspect.currentframe()).filename
path = os.path.dirname(os.path.abspath(filename))
Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • 5
    I think this is indeed the most robust method, but I question the OP's stated need for this. I often see developers do this when they are using data files in locations relative to the executing module, but IMO data files should be put in a known location. – Ryan Ginstrom Jun 03 '11 at 01:47
  • 19
    @Ryan LOL, if would be great if you would be able to define a "known location" that is multiplatform and that also comes with the module. I'm ready to bet that the only safe location is the script location. Note, this doesn't meat that the script should write to this location, but for reading data it is safe. – sorin Nov 22 '11 at 11:31
  • 2
    Still, the solution is not good, just try to call `chdir()` before the function, it will change the result. Also calling the python script from another directory will alter the result, so it's not a good solution. – sorin Nov 22 '11 at 11:41
  • 2
    `os.path.expanduser("~")` is a cross-platform way to get the user's directory. Unfortunately, that isn't the Windows best practice for where to stick application data. – Ryan Ginstrom Dec 01 '11 at 04:35
  • 7
    @sorin: I've tried `chdir()` before running the script; it produces correct result. I've tried calling the script from another directory and it also works. The results are the same as [`inspect.getabsfile()`-based solution](http://stackoverflow.com/a/22881871/4279). – jfs Apr 05 '14 at 14:08
  • 1
    This method even works when running from a python-based framework, while __file__ doesn't. – Jetse Jun 13 '14 at 10:16
66

In Python 3.4+ you can use the simpler pathlib module:

from inspect import currentframe, getframeinfo
from pathlib import Path

filename = getframeinfo(currentframe()).filename
parent = Path(filename).resolve().parent

You can also use __file__ (when it's available) to avoid the inspect module altogether:

from pathlib import Path
parent = Path(__file__).resolve().parent
Eugene Yarmash
  • 142,882
  • 41
  • 325
  • 378
  • On Windows (10), I got this error: `TypeError: unsupported operand type(s) for +: 'WindowsPath' and 'str'` when I tried appending another string to the file path with the `+` operator. A workaround that worked was wrapping `*parent*` in `str()` function. – Dut A. Jul 04 '18 at 05:54
  • 4
    @Dut A. You should use [`.joinpath()`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.joinpath) (or the `/` operator) for this, not `+`. – Eugene Yarmash Aug 27 '18 at 12:32
  • 2
    NOTE: You have to use resolve() if you want an absolute path without symlinks. Docs [here](https://docs.python.org/3/library/pathlib.html#pathlib.Path.resolve) – Jesuisme Apr 21 '21 at 16:52
  • In any case where ```__file__``` is not available, and for simplicity's sake, you can substitute ```__file__``` with dot ('.') when using pathlib.Path. So, the current working directory is ```directory = Path('.').resolve()``` with ```parent = Path('..')resolve()```. – Tim Pozza Jan 01 '22 at 02:57
  • @TimPozza: Sure, you can do that, but it's not necessarily the directory of the current script. – martineau Apr 18 '22 at 16:35
53
#!/usr/bin/env python
import inspect
import os
import sys

def get_script_dir(follow_symlinks=True):
    if getattr(sys, 'frozen', False): # py2exe, PyInstaller, cx_Freeze
        path = os.path.abspath(sys.executable)
    else:
        path = inspect.getabsfile(get_script_dir)
    if follow_symlinks:
        path = os.path.realpath(path)
    return os.path.dirname(path)

print(get_script_dir())

It works on CPython, Jython, Pypy. It works if the script is executed using execfile() (sys.argv[0] and __file__ -based solutions would fail here). It works if the script is inside an executable zip file (/an egg). It works if the script is "imported" (PYTHONPATH=/path/to/library.zip python -mscript_to_run) from a zip file; it returns the archive path in this case. It works if the script is compiled into a standalone executable (sys.frozen). It works for symlinks (realpath eliminates symbolic links). It works in an interactive interpreter; it returns the current working directory in this case.

Community
  • 1
  • 1
jfs
  • 399,953
  • 195
  • 994
  • 1,670
  • Works perfectly well with PyInstaller. – gaborous Aug 16 '15 at 01:43
  • 2
    Is there any reason why `getabsfile(..)` is not mentioned in [the documentation for `inspect`](https://docs.python.org/2/library/inspect.html)? It appears in the source that's linked off that page. – Evgeni Sergeev Nov 14 '15 at 12:39
  • @EvgeniSergeev it might be a bug. It is a simple wrapper around `getsourcefile()`, `getfile()` that are documented. – jfs Nov 14 '15 at 13:25
45

The os.path... approach was the 'done thing' in Python 2.

In Python 3, you can find directory of script as follows:

from pathlib import Path
script_path = Path(__file__).parent
Serge Stroobandt
  • 28,495
  • 9
  • 107
  • 102
Ron Kalian
  • 3,280
  • 3
  • 15
  • 23
  • 19
    Or just `Path(__file__).parent`. But `cwd` is a misnomer, that's not the _current working directory_, but the _file's directory_. They could be the same, but that's not usually the case. – Nuno André Mar 17 '19 at 21:01
  • 1
    NOTE: This will only return a relative path. For an absolute path, you must use resolve(). Docs [here](https://docs.python.org/3/library/pathlib.html#pathlib.Path.resolve) – Jesuisme Apr 21 '21 at 16:53
9

Note: this answer is now a package (also with safe relative importing capabilities)

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

Alternative:

If you know for certain that __file__ is available to your surrounding code, you can simplify to this:

  • >= Python 3.4: this_dir = Path(__file__).absolute().parent
  • >= Python 2: this_dir = os.path.dirname(os.path.abspath(__file__))
Simon Streicher
  • 2,638
  • 1
  • 26
  • 30
  • Hello @Simon Streicher, thanks for your answer. Does the 'locate' library deal with changing the current working directory (os.chdir)? – Jako Dec 31 '22 at 12:16
  • 1
    Hi @Jako. Yes, for normal use cases locate works separately from the current working directory and is therefore unaffected by things like `os.chdir`. No, when used from an interactive Python session! It serves two functionalities: 1. The `get_dir` function, which will return the directory of the current script (or `os.getcwd()` when used interactively). 2. `prepend_sys_path` (or `append_sys_path`), which will evaluate all paths relative to `get_dir` and then add it to `sys.path` (more importantly, it allows for temporary effect: `with prepend_sys_path("../foopath"): import foo`) – Simon Streicher Jan 03 '23 at 08:47
8

Would

import os
cwd = os.getcwd()

do what you want? I'm not sure what exactly you mean by the "current script directory". What would the expected output be for the use cases you gave?

Will McCutchen
  • 13,047
  • 3
  • 44
  • 43
  • 3
    It wouldn't help. I believe @bogdan is looking for the directory for the script that is at the top of the call stack. i.e. in all his/her cases, it should print the directory where 'myfile.py' sits. Yet your method would only print the directory of the file that calls `exec('myfile.py')`, same as `__file__` and `sys.argv[0]`. – Zhang18 Sep 15 '10 at 15:03
  • Yeah, that makes sense. I just wanted to make sure @bogdan wasn't overlooking something simple, and I couldn't tell exactly what they wanted. – Will McCutchen Sep 15 '10 at 15:23
5

Just use os.path.dirname(os.path.abspath(__file__)) and examine very carefully whether there is a real need for the case where exec is used. It could be a sign of troubled design if you are not able to use your script as a module.

Keep in mind Zen of Python #8, and if you believe there is a good argument for a use-case where it must work for exec, then please let us know some more details about the background of the problem.

wim
  • 338,267
  • 99
  • 616
  • 750
  • 2
    If you do not running with exec() you will loose debugger context. Also exec() is supposed to be considerably faster than starting a new process. – sorin Nov 22 '11 at 11:52
  • @sorin It's not a question of exec vs starting a new process, so that's a strawman argument. It's a question of exec vs using an import or function call. – wim Jan 23 '19 at 17:14
4

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

muxator
  • 348
  • 3
  • 8
3

First.. a couple missing use-cases here if we're talking about ways to inject anonymous code..

code.compile_command()
code.interact()
imp.load_compiled()
imp.load_dynamic()
imp.load_module()
__builtin__.compile()
loading C compiled shared objects? example: _socket?)

But, the real question is, what is your goal - are you trying to enforce some sort of security? Or are you just interested in whats being loaded.

If you're interested in security, the filename that is being imported via exec/execfile is inconsequential - you should use rexec, which offers the following:

This module contains the RExec class, which supports r_eval(), r_execfile(), r_exec(), and r_import() methods, which are restricted versions of the standard Python functions eval(), execfile() and the exec and import statements. Code executed in this restricted environment will only have access to modules and functions that are deemed safe; you can subclass RExec add or remove capabilities as desired.

However, if this is more of an academic pursuit.. here are a couple goofy approaches that you might be able to dig a little deeper into..

Example scripts:

./deep.py

print ' >> level 1'
execfile('deeper.py')
print ' << level 1'

./deeper.py

print '\t >> level 2'
exec("import sys; sys.path.append('/tmp'); import deepest")
print '\t << level 2'

/tmp/deepest.py

print '\t\t >> level 3'
print '\t\t\t I can see the earths core.'
print '\t\t << level 3'

./codespy.py

import sys, os

def overseer(frame, event, arg):
    print "loaded(%s)" % os.path.abspath(frame.f_code.co_filename)

sys.settrace(overseer)
execfile("deep.py")
sys.exit(0)

Output

loaded(/Users/synthesizerpatel/deep.py)
>> level 1
loaded(/Users/synthesizerpatel/deeper.py)
    >> level 2
loaded(/Users/synthesizerpatel/<string>)
loaded(/tmp/deepest.py)
        >> level 3
            I can see the earths core.
        << level 3
    << level 2
<< level 1

Of course, this is a resource-intensive way to do it, you'd be tracing all your code.. Not very efficient. But, I think it's a novel approach since it continues to work even as you get deeper into the nest. You can't override 'eval'. Although you can override execfile().

Note, this approach only coveres exec/execfile, not 'import'. For higher level 'module' load hooking you might be able to use use sys.path_hooks (Write-up courtesy of PyMOTW).

Thats all I have off the top of my head.

synthesizerpatel
  • 27,321
  • 5
  • 74
  • 91
3

Here is a partial solution, still better than all published ones so far.

import sys, os, os.path, inspect

#os.chdir("..")

if '__file__' not in locals():
    __file__ = inspect.getframeinfo(inspect.currentframe())[0]

print os.path.dirname(os.path.abspath(__file__))

Now this works will all calls but if someone use chdir() to change the current directory, this will also fail.

Notes:

  • sys.argv[0] is not going to work, will return -c if you execute the script with python -c "execfile('path-tester.py')"
  • I published a complete test at https://gist.github.com/1385555 and you are welcome to improve it.
sorin
  • 161,544
  • 178
  • 535
  • 806
1

This should work in most cases:

import os,sys
dirname=os.path.dirname(os.path.realpath(sys.argv[0]))
Jahid
  • 21,542
  • 10
  • 90
  • 108
  • 5
    This solution uses the current directory and it's explicitly stated in the question that such solution will fail. – skyking Dec 06 '16 at 09:42
1

Hopefully this helps:- If you run a script/module from anywhere you'll be able to access the __file__ variable which is a module variable representing the location of the script.

On the other hand, if you're using the interpreter you don't have access to that variable, where you'll get a name NameError and os.getcwd() will give you the incorrect directory if you're running the file from somewhere else.

This solution should give you what you're looking for in all cases:

from inspect import getsourcefile
from os.path import abspath
abspath(getsourcefile(lambda:0))

I haven't thoroughly tested it but it solved my problem.

Community
  • 1
  • 1
Mark
  • 19
  • 3
1

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
Soap
  • 309
  • 2
  • 14
0

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.

this_dir = '/'.join(__file__.split('/')[:-1])
print(this_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.

Simon Streicher
  • 2,638
  • 1
  • 26
  • 30
touhidurrr
  • 318
  • 2
  • 11
  • Are you sure of the code? it did not print anything – Chadee Fouad Aug 12 '22 at 04:28
  • Pretty sure. I tested on Python 3.x though. – touhidurrr Sep 11 '22 at 03:05
  • 1
    @ChadeeFouad I think the reason it didn't print anything is that your working directory is the same as the file directory, so `'filename.py'.split('/')` → `['filename.py']` and then `'/'.join(['filename.py'][:-1])` → `''`. So basically you printed blank. – Simon Streicher Nov 02 '22 at 07:15
  • It should also be noted that this can return a relative filepath, so it should be used within the immediate current working directory context. Also if you run this from Windows and use a `'\'` delimited full path (e.g. `python c:\path\to\directory\filepath.py`), the result will incorrectly be blank. – Simon Streicher Nov 02 '22 at 07:24
  • A Windows/*nix solution would be `'/'.join(__file__.replace('\\', '/')split('/')[:-1])`, but this might still end up being a relative path (with the possibility of a blank string, though blank string shouldn't cause problems). It should still not be used outside of your current working directory context (e.g. don't add it to `sys.path`, don't use it within a new `chdir` context) – Simon Streicher Nov 02 '22 at 07:36
  • Also, `os` is a buildin module and would always be available, so it shouldn't be a barrier to entry to import it. If you are for some reason just looking for a oneliner, here are two options using the `os` module directly: `__import__("os").path.dirname(__import__("os").path.abspath(__file__))` or `(lambda p: p.dirname(p.abspath(__file__)))(__import__("os").path)` – Simon Streicher Nov 02 '22 at 08:14
  • returns `NameError: name '__file__' is not defined` on my end – Johan May 12 '23 at 16:03
-1
print(__import__("pathlib").Path(__file__).parent)
BaiJiFeiLong
  • 3,716
  • 1
  • 30
  • 28