Relative paths in Python [duplicate]
Asked Answered
C

21

428

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?

Clerissa answered 27/5, 2009 at 21:43 Comment(5)
Similar questions: #52020 #7166249 #3562191Autophyte
See also What exactly is current working directory?Osithe
The (only) answer below using pathlib (instead of os) is hereLysine
@Lysine Did you mean here?Talyah
@Autophyte the last one isn't similar at all.Hoodoo
H
546

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.

Haem answered 27/5, 2009 at 21:47 Comment(11)
Am I correct in thinking that file is not always available (e.g. when you run the file directly rather than importing it)?Angy
@Stephen Edmonds I'm using it a file that I run, rather than import, and it works great.Clerissa
Note you should use os.path.join everywhere for portability: filename = os.path.join(dir, 'relative', 'path', 'to', 'file', 'you' , 'want')Exum
os.path.dirname(__file__) can give an empty string, use os.path.dirname(os.path.abspath(__file__)) insteadHenrik
It's a minor thing, but PLEASE don't use dir as a variable name since it is a builtin.Massie
os.path.abspath(os.path.join(cur_path, os.pardir))Rub
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 MacOSPyuria
@DmitryTrofimov If we omit the os.path.abspath, we still get a valid relative path from the working directory, correct?Impedimenta
What is file in __file__ .Joon
@Joon 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://mcmap.net/q/14475/-import-a-module-from-a-relative-pathFledge
Indeedn it fails for meJoon
F
116

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)
Freespoken answered 3/7, 2018 at 7:40 Comment(3)
In summary: from pathlib import Path script_dir=Path(__file__).parent template_path=(script_dir / template_name).resolve()Bumpy
@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.Pipestone
Then again, since the past, __file__ can fail and should not be used. Instead, use ~inspect.getfile(inspect.currentframe()) (after import inspect): https://mcmap.net/q/14475/-import-a-module-from-a-relative-pathFledge
P
77

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

Pablopabon answered 28/4, 2016 at 6:22 Comment(4)
Yes using os.path.join is better because it joins them with the OS-specific separator.Imprint
'/relative/path...' is not a relative path. Is that intentional?Brame
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.Pipestone
@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.Isola
D
76

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

import sys,os
sys.path.append(os.path.realpath('..'))
Delinda answered 19/3, 2012 at 10:24 Comment(4)
os.path.dirname(__file__) gave me an empty string. This worked perfectly.Aretta
This seems to give the parent of the directory the script is run from, not of the script's location.Oxyhydrogen
os.path.realpath('..') gives you the parent directory of the current working dir. That's usually not what you want.Pipestone
@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.Pipestone
S
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)
Shuttlecock answered 6/10, 2015 at 15:17 Comment(2)
When I run this in windows, I recieve an error: FileNotFoundError: [Errno 2] No such file or directory: '<path>' where <path> has the correct path segments but uses \\ for separators.Outpoint
i was able to get a relative path by using filename = os.path.abspath('../Folder2/same.txt')Sense
B
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'
Beast answered 27/5, 2009 at 21:47 Comment(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.Haem
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.Beast
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.Pipestone
F
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://mcmap.net/q/13307/-how-do-i-get-the-path-and-name-of-the-python-file-that-is-currently-executing and https://mcmap.net/q/13307/-how-do-i-get-the-path-and-name-of-the-python-file-that-is-currently-executing

Falcon answered 18/2, 2016 at 21:41 Comment(1)
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__.Pipestone
L
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()
Lumpkin answered 16/10, 2020 at 9:38 Comment(0)
S
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.

Sextain answered 10/9, 2020 at 15:9 Comment(0)
H
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"

Hapte answered 7/12, 2013 at 4:30 Comment(0)
S
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.

Scrambler answered 24/3, 2016 at 20:3 Comment(1)
Instead of re-importing, you'd use sys.modules['__main__'].__file__.Pipestone
J
3

An alternative which works for me:

this_dir = os.path.dirname(__file__) 
filename = os.path.realpath("{0}/relative/file.path".format(this_dir))
Jameyjami answered 10/10, 2014 at 9:19 Comment(0)
G
1

A simple solution would be

import os
os.chdir(os.path.dirname(__file__))
Galvez answered 4/2, 2021 at 22:21 Comment(0)
S
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:

Scalawag answered 28/10, 2021 at 16:38 Comment(0)
U
1

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

Unpleasantness answered 11/11, 2021 at 1:57 Comment(0)
B
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, '../')
Bemoan answered 16/3, 2017 at 18:14 Comment(1)
This relies on the current working directory, which could be radically different from what you actually want.Pipestone
E
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')
Excess answered 15/10, 2020 at 0:8 Comment(0)
L
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
Lunneta answered 9/4, 2021 at 6:56 Comment(0)
B
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".

Bitternut answered 17/11, 2021 at 6:17 Comment(0)
U
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)]

Ultrasonic answered 26/1, 2022 at 22:46 Comment(0)
G
-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)

Guelders answered 4/6, 2014 at 14:41 Comment(1)
right, so this will work from the CWD which is not what the OP asks for. The want to work from the scripts location.Guelders

© 2022 - 2024 — McMap. All rights reserved.