415

I'm building a simple helper script for work that will copy a couple of template files in our code base to the current directory. I don't, however, have the absolute path to the directory where the templates are stored. I do have a relative path from the script but when I call the script it treats that as a path relative to the current working directory. Is there a way to specify that this relative url is from the location of the script instead?

baudtack
  • 29,062
  • 9
  • 53
  • 61
  • 1
    Similar questions: https://stackoverflow.com/questions/51520/how-to-get-an-absolute-file-path-in-python https://stackoverflow.com/questions/7165749/open-file-in-a-relative-location-in-python https://stackoverflow.com/questions/3561691/python-syntaxerror-eol-while-scanning-string-literal – Raj Oct 12 '17 at 21:24
  • See also [What exactly is current working directory?](https://stackoverflow.com/questions/45591428/what-exactly-is-current-working-directory) – tripleee Sep 01 '21 at 04:49
  • The (only) answer below using pathlib (instead of os) [is here](https://stackoverflow.com/a/36906785/6069586) – JWCS Jan 27 '22 at 18:05
  • 2
    @JWCS Did you mean [here](https://stackoverflow.com/a/51149057/1707427)? – Sören Apr 30 '22 at 18:55
  • @Raj the last one isn't similar at all. – Karl Knechtel Sep 04 '22 at 00:51

21 Answers21

534

In the file that has the script, you want to do something like this:

import os
dirname = os.path.dirname(__file__)
filename = os.path.join(dirname, 'relative/path/to/file/you/want')

This will give you the absolute path to the file you're looking for. Note that if you're using setuptools, you should probably use its package resources API instead.

UPDATE: I'm responding to a comment here so I can paste a code sample. :-)

Am I correct in thinking that __file__ is not always available (e.g. when you run the file directly rather than importing it)?

I'm assuming you mean the __main__ script when you mention running the file directly. If so, that doesn't appear to be the case on my system (python 2.5.1 on OS X 10.5.7):

#foo.py
import os
print os.getcwd()
print __file__

#in the interactive interpreter
>>> import foo
/Users/jason
foo.py

#and finally, at the shell:
~ % python foo.py
/Users/jason
foo.py

However, I do know that there are some quirks with __file__ on C extensions. For example, I can do this on my Mac:

>>> import collections #note that collections is a C extension in Python 2.5
>>> collections.__file__
'/System/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/lib-
dynload/collections.so'

However, this raises an exception on my Windows machine.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Jason Baker
  • 192,085
  • 135
  • 376
  • 510
  • 1
    Am I correct in thinking that __file__ is not always available (e.g. when you run the file directly rather than importing it)? – Stephen Edmonds May 28 '09 at 12:43
  • @Stephen Edmonds I'm using it a file that I run, rather than import, and it works great. – baudtack Jun 04 '09 at 03:37
  • 27
    Note you should use os.path.join everywhere for portability: `filename = os.path.join(dir, 'relative', 'path', 'to', 'file', 'you' , 'want')` – ford Feb 06 '14 at 17:51
  • 34
    `os.path.dirname(__file__)` can give an empty string, use `os.path.dirname(os.path.abspath(__file__))` instead – Dmitry Trofimov Mar 10 '15 at 22:03
  • 22
    It's a minor thing, but PLEASE don't use dir as a variable name since it is a builtin. – David Jul 14 '15 at 19:18
  • `os.path.abspath(os.path.join(cur_path, os.pardir))` – Tinkaal Gogoi Jun 15 '20 at 09:45
  • This is a good solution, but it won't work unless you take Dimitry Troflimov's comment, please updated your answer to reflect that. os.path.dirname(os.path.abspath(__file__)). This is an issue I had in both Ubuntu 20.0.4 and MacOS – Amro Younes Mar 29 '21 at 14:19
  • @DmitryTrofimov If we omit the os.path.abspath, we still get a valid *relative* path from the working directory, correct? – Duncan MacIntyre Jul 30 '21 at 05:51
  • What is `file` in `__file__` . – Julien Aug 12 '22 at 08:14
  • @Julien The special variable `__file__` contains the path (directory + file name) of the currently running script. Note that `__file__` can fail and should be replaced by `inspect.getfile(inspect.currentframe())` (after `import inspect`): https://stackoverflow.com/a/6098238/1386750 – AstroFloyd Aug 14 '22 at 06:36
  • Indeedn it fails for me – Julien Aug 14 '22 at 07:52
105

It's 2018 now, and Python has already evolved to the __future__ long time ago. So how about using the amazing pathlib coming with Python 3.4 to accomplish the task instead of struggling with os, os.path, glob , shutil, etc.

So we have 3 paths here (possibly duplicated):

  • mod_path: which is the path of the simple helper script
  • src_path: which contains a couple of template files waiting to be copied.
  • cwd: current directory, the destination of those template files.

and the problem is: we don't have the full path of src_path, only know its relative path to the mod_path.

Now let's solve this with the amazing pathlib:

# Hope you don't be imprisoned by legacy Python code :)
from pathlib import Path

# `cwd`: current directory is straightforward
cwd = Path.cwd()

# `mod_path`: According to the accepted answer and combine with future power
# if we are in the `helper_script.py`
mod_path = Path(__file__).parent
# OR if we are `import helper_script`
mod_path = Path(helper_script.__file__).parent

# `src_path`: with the future power, it's just so straightforward
relative_path_1 = 'same/parent/with/helper/script/'
relative_path_2 = '../../or/any/level/up/'
src_path_1 = (mod_path / relative_path_1).resolve()
src_path_2 = (mod_path / relative_path_2).resolve()

In the future, it's just that simple.


Moreover, we can select and check and copy/move those template files with pathlib:

if src_path != cwd:
    # When we have different types of files in the `src_path`
    for template_path in src_path.glob('*.ini'):
        fname = template_path.name
        target = cwd / fname
        if not target.exists():
            # This is the COPY action
            with target.open(mode='wb') as fd:
                fd.write(template_path.read_bytes())
            # If we want MOVE action, we could use:
            # template_path.replace(target)
Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
YaOzI
  • 16,128
  • 9
  • 76
  • 72
  • 9
    In summary: `from pathlib import Path` `script_dir=Path(__file__).parent` `template_path=(script_dir / template_name).resolve()` – a113nw Jun 30 '20 at 15:06
  • 1
    @4myle: resolve the `__file__` path _first_, for scripts it is possibly the relative path given to the interpreter. So `script_dir = Path(__file__).resolve().parent`. From there on out `script_dir` is absolute, and everything else can be built on top of that. Further `resolve()` calls are only needed if you need to resolve symlinks. – Martijn Pieters Aug 17 '21 at 13:12
  • Then again, since the __past__, `__file__` can fail and should not be used. Instead, use `~inspect.getfile(inspect.currentframe())` (after `import inspect`): https://stackoverflow.com/a/6098238/1386750 – AstroFloyd Aug 14 '22 at 06:06
76

you need os.path.realpath (sample below adds the parent directory to your path)

import sys,os
sys.path.append(os.path.realpath('..'))
user989762
  • 1,686
  • 3
  • 16
  • 20
  • 3
    `os.path.dirname(__file__)` gave me an empty string. This worked perfectly. – Darragh Enright Feb 21 '14 at 10:51
  • 5
    This seems to give the parent of the directory the script is run from, not of the script's location. – Coquelicot Jul 17 '17 at 03:50
  • 25
    `os.path.realpath('..')` gives you the parent directory of the **current working dir**. That's usually **not** what you want. – Martijn Pieters May 12 '18 at 18:55
  • 1
    @DarraghEnright: That only happens in a Python-script-to-exe packaging environment. That's one of the rare exceptions where relying on the current working dir would be the alternative. – Martijn Pieters May 12 '18 at 18:56
75

As mentioned in the accepted answer

import os
dir = os.path.dirname(__file__)
filename = os.path.join(dir, '/relative/path/to/file/you/want')

I just want to add that

the latter string can't begin with the backslash , infact no string should include a backslash

It should be something like

import os
dir = os.path.dirname(__file__)
filename = os.path.join(dir, 'relative','path','to','file','you','want')

The accepted answer can be misleading in some cases , please refer to this link for details

Community
  • 1
  • 1
Ahmed
  • 2,176
  • 5
  • 26
  • 40
  • 4
    Yes using `os.path.join` is better because it joins them with the OS-specific separator. – Farshid T Sep 04 '16 at 09:16
  • 1
    `'/relative/path...'` is not a relative path. Is that intentional? – steveire Feb 13 '18 at 12:24
  • 3
    This answer is now outdated, as the top answer has been edited to use a proper relative path in `os.path.join()`. What is left is the preference to use separate strings for each path element over hardcoding the path separator. – Martijn Pieters May 12 '18 at 18:57
  • 1
    @MartijnPieters Yes, the top answer has been edited to match this in part, but the separate strings is not a preference - separating the stings like this makes it os-independent. – jshrimp29 Jul 03 '19 at 22:39
16

Consider my code:

import os


def readFile(filename):
    filehandle = open(filename)
    print filehandle.read()
    filehandle.close()



fileDir = os.path.dirname(os.path.realpath('__file__'))
print fileDir

#For accessing the file in the same folder
filename = "same.txt"
readFile(filename)

#For accessing the file in a folder contained in the current folder
filename = os.path.join(fileDir, 'Folder1.1/same.txt')
readFile(filename)

#For accessing the file in the parent folder of the current folder
filename = os.path.join(fileDir, '../same.txt')
readFile(filename)

#For accessing the file inside a sibling folder.
filename = os.path.join(fileDir, '../Folder2/same.txt')
filename = os.path.abspath(os.path.realpath(filename))
print filename
readFile(filename)
Fahad Haleem
  • 689
  • 6
  • 5
  • When I run this in windows, I recieve an error: FileNotFoundError: [Errno 2] No such file or directory: '' where has the correct path segments but uses \\ for separators. – lonstar Jul 24 '19 at 05:09
  • i was able to get a relative path by using ```filename = os.path.abspath('../Folder2/same.txt')``` – Nicholas_Jones Jun 12 '21 at 00:00
12

See sys.path As initialized upon program startup, the first item of this list, path[0], is the directory containing the script that was used to invoke the Python interpreter.

Use this path as the root folder from which you apply your relative path

>>> import sys
>>> import os.path
>>> sys.path[0]
'C:\\Python25\\Lib\\idlelib'
>>> os.path.relpath(sys.path[0], "path_to_libs") # if you have python 2.6
>>> os.path.join(sys.path[0], "path_to_libs")
'C:\\Python25\\Lib\\idlelib\\path_to_libs'
Tom Leys
  • 18,473
  • 7
  • 40
  • 62
  • 3
    That's not necessarily true. Usually sys.path[0] is an empty string or a dot, which is a relative path to the current directory. If you want the current directory, use os.getcwd. – Jason Baker May 27 '09 at 21:54
  • The original poster commented that the current working directory is the wrong place to base the relative path from. You are correct in saying that sys.path[0] is not always valid. – Tom Leys May 28 '09 at 00:56
  • No, `sys.path[0]` is not always set to the parent directory. Python code can be invoked with `-c` or `-m` or via an embedded interpreter, at which point `sys.path[0]` is set to something different altogether. – Martijn Pieters May 12 '18 at 18:58
8

Instead of using

import os
dirname = os.path.dirname(__file__)
filename = os.path.join(dirname, 'relative/path/to/file/you/want')

as in the accepted answer, it would be more robust to use:

import inspect
import os
dirname = os.path.dirname(os.path.abspath(inspect.stack()[0][1]))
filename = os.path.join(dirname, 'relative/path/to/file/you/want')

because using __file__ will return the file from which the module was loaded, if it was loaded from a file, so if the file with the script is called from elsewhere, the directory returned will not be correct.

These answers give more detail: https://stackoverflow.com/a/31867043/5542253 and https://stackoverflow.com/a/50502/5542253

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
kmt
  • 773
  • 12
  • 30
  • 5
    `inspect.stack()` is an *expensive* function to call. It retrieves info for all stack frames, which you then discard and only get the top one for. It basically calls `inspect.getfile()` on the module object, which just returns `module.__file__`. You are far better of just using `__file__`. – Martijn Pieters May 12 '18 at 19:05
8

From what suggest others and from pathlib documentation, a simple (but not ideal) solution is the following (suppose the file we need to refer to is Test/data/users.csv):

# Current file location: Tests/src/long/module/subdir/some_script.py
from pathlib import Path

# back to Tests/
PROJECT_ROOT = Path(__file__).parents[4]
# then down to Test/data/users.csv
CSV_USERS_PATH = PROJECT_ROOT / 'data' / 'users.csv'  

with CSV_USERS_PATH.open() as users:
    print(users.read())

This works but looks a bit odd because if you move some_script.py around, the path to the root of our project may change (and we would therefore need to change the parents[4] part).

I think I found a better solution that, based on the same idea. We will use a file paths.py to store where the root of the project is, this file will remain at the same location compared to the root directory.

Tests
├── data
│  └── users.csv
└── src
   ├── long
   │  └── module
   │     └── subdir
   │        └── some_script.py
   ├── main.py
   └── paths.py

Where paths.py's only responsability is to provide PROJECT_ROOT:

from pathlib import Path

PROJECT_ROOT = Path(__file__).parents[1]

All scripts can now use paths.PROJECT_ROOT to express absolute paths from the root of the project. For example in src/long/module/subdir/some_script.py we could have:

from paths import PROJECT_ROOT

CSV_USERS_PATH = PROJECT_ROOT / 'data' / 'users.csv'

def hello():
    with CSV_USERS_PATH.open() as f:
        print(f.read())

And everything goes as expected:

~/Tests/src/$ python main.py

/Users/cglacet/Tests/data/users.csv
hello, user

~/Tests/$ python src/main.py

/Users/cglacet/Tests/data/users.csv
hello, user

The main.py script simply is:

from long.module.subdir import some_script

some_script.hello()
cglacet
  • 8,873
  • 4
  • 45
  • 60
6

summary of the most important commands

>>> import os
>>> os.path.join('/home/user/tmp', 'subfolder')
'/home/user/tmp/subfolder'
>>> os.path.normpath('/home/user/tmp/../test/..')
'/home/user'
>>> os.path.relpath('/home/user/tmp', '/home/user')
'tmp'
>>> os.path.isabs('/home/user/tmp')
True
>>> os.path.isabs('/tmp')
True
>>> os.path.isabs('tmp')
False
>>> os.path.isabs('./../tmp')
False
>>> os.path.realpath('/home/user/tmp/../test/..') # follows symbolic links
'/home/user'

A detailed description is found in the docs. These are linux paths. Windows should work analogous.

Markus Dutschke
  • 9,341
  • 4
  • 63
  • 58
4

Hi first of all you should understand functions os.path.abspath(path) and os.path.relpath(path)

In short os.path.abspath(path) makes a relative path to absolute path. And if the path provided is itself a absolute path then the function returns the same path.

similarly os.path.relpath(path) makes a absolute path to relative path. And if the path provided is itself a relative path then the function returns the same path.

Below example can let you understand the above concept properly:

suppose i have a file input_file_list.txt which contains list of input files to be processed by my python script.

D:\conc\input1.dic

D:\conc\input2.dic

D:\Copyioconc\input_file_list.txt

If you see above folder structure, input_file_list.txt is present in Copyofconc folder and the files to be processed by the python script are present in conc folder

But the content of the file input_file_list.txt is as shown below:

..\conc\input1.dic

..\conc\input2.dic

And my python script is present in D: drive.

And the relative path provided in the input_file_list.txt file are relative to the path of input_file_list.txt file.

So when python script shall executed the current working directory (use os.getcwd() to get the path)

As my relative path is relative to input_file_list.txt, that is "D:\Copyofconc", i have to change the current working directory to "D:\Copyofconc".

So i have to use os.chdir('D:\Copyofconc'), so the current working directory shall be "D:\Copyofconc".

Now to get the files input1.dic and input2.dic, i will read the lines "..\conc\input1.dic" then shall use the command

input1_path= os.path.abspath('..\conc\input1.dic') (to change relative path to absolute path. Here as current working directory is "D:\Copyofconc", the file ".\conc\input1.dic" shall be accessed relative to "D:\Copyofconc")

so input1_path shall be "D:\conc\input1.dic"

4

This code will return the absolute path to the main script.

import os
def whereAmI():
    return os.path.dirname(os.path.realpath(__import__("__main__").__file__))

This will work even in a module.

BookOwl
  • 378
  • 3
  • 11
3

An alternative which works for me:

this_dir = os.path.dirname(__file__) 
filename = os.path.realpath("{0}/relative/file.path".format(this_dir))
J0hnG4lt
  • 4,337
  • 5
  • 24
  • 40
1

Example


Here's an example, tested in Python '3.9.5`:

your current directory: 'c:\project1\code\'
and you want to access the following folder: 'c:\project1\dataset\train\'.
Then you can access the folder using the following address: '../dataset/train/'

References


If you want some more information about path in Python, read this:

0

What worked for me is using sys.path.insert. Then I specified the directory I needed to go. For example I just needed to go up one directory.

import sys
sys.path.insert(0, '../')
Whitecat
  • 3,882
  • 7
  • 48
  • 78
0

I think to work with all systems use "ntpath" instead of "os.path". Today, it works well with Windows, Linux and Mac OSX.

import ntpath
import os
dirname = ntpath.dirname(__file__)
filename = os.path.join(dirname, 'relative/path/to/file/you/want')
jeugregg
  • 116
  • 6
0

A simple solution would be

import os
os.chdir(os.path.dirname(__file__))
Qin Heyang
  • 1,456
  • 1
  • 16
  • 18
0

From C:\Users\xyz\myFolder to C:\Users\xyz\testdata :

import os
working_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
# C:\Users\xyz\myFolder
print(working_dir)
updated_working_dir = os.path.join(os.path.realpath(working_dir + '/../'), 'testdata')
# C:\Users\xyz\testdata
print(updated_working_dir)

Output

C:\Users\xyz\myFolder
C:\Users\xyz\testdata
Shivam Bharadwaj
  • 1,864
  • 21
  • 23
0

Here is my sumup:

First, define the tool function named relpath, which convert a relative path to current file into a relative path to cwd

import os
relpath = lambda p: os.path.normpath(os.path.join(os.path.dirname(__file__), p))

Then we use it to wrap paths which is relative to current file

path1 = relpath('../src/main.py')

And you can also call sys.path.append() to import file relative to current file position

sys.path.append(relpath('..')) # so that you can import from upper dir

The full example code : https://gist.github.com/luochen1990/9b1ffa30f5c4a721dab5991e040e3eb1

luochen1990
  • 3,689
  • 1
  • 22
  • 37
0

Say the current archive named "Helper" and the upper directory named "Workshop", and the template files are in \Workshop\Templates, then the relative path in Python is "..\Templates".

maggie2000
  • 81
  • 4
0

This a simple way to add a relative path to the system path set . For example, for frequent case when the target directory is one level above (thus, '/../') the working directory:

import os
import sys
workingDir = os.getcwd()
targetDir = os.path.join(os.path.relpath(workingDir + '/../'),'target_directory')
sys.path.insert(0,targetDir)

This solution was tested for:

Python 3.9.6 | packaged by conda-forge | (default, Jul 11 2021, 03:37:25) [MSC v.1916 64 bit (AMD64)]

DanGitR
  • 47
  • 1
  • 8
-1

I'm not sure if this applies to some of the older versions, but I believe Python 3.3 has native relative path support.

For example the following code should create a text file in the same folder as the python script:

open("text_file_name.txt", "w+t")

(note that there shouldn't be a forward or backslash at the beginning if it's a relative path)

Samie Bencherif
  • 1,285
  • 12
  • 27