162

I have a directory full of scripts (let's say project/bin). I also have a library located in project/lib and want the scripts to automatically load it. This is what I normally use at the top of each script:

#!/usr/bin/python
from os.path import dirname, realpath, sep, pardir
import sys
sys.path.append(dirname(realpath(__file__)) + sep + pardir + sep + "lib")

# ... now the real code
import mylib

This is kind of cumbersome, ugly, and has to be pasted at the beginning of every file. Is there a better way to do this?

Really what I'm hoping for is something as smooth as this:

#!/usr/bin/python
import sys.path
from os.path import pardir, sep
sys.path.append_relative(pardir + sep + "lib")

import mylib

Or even better, something that wouldn't break when my editor (or someone else who has commit access) decides to reorder the imports as part of its clean-up process:

#!/usr/bin/python --relpath_append ../lib
import mylib

That wouldn't port directly to non-posix platforms, but it would keep things clean.

2
  • 2
    See also: stackoverflow.com/questions/2349991/… Commented May 18, 2016 at 22:15
  • 1
    I would advise readers to check out the @EyalLevin answer below as it sets up the path at the command line invocation of your script and avoids touching the shell environment settings completely. You don't have to bake in any path dependencies into your committed code either. Commented Dec 6, 2021 at 15:25

13 Answers 13

170

This is what I use:

import os, sys
sys.path.append(os.path.join(os.path.dirname(__file__), "lib"))
Sign up to request clarification or add additional context in comments.

3 Comments

I would do sys.path.insert(0, ..) so that it doesn't get overridden by other paths.
Is there really no way to get this to run automatically?
This is slightly risky. If __file__ is a relative filename relative to the current working directory (e.g., setup.py), then os.path.dirname(__file__) will be the empty string. For this and similar concerns raised by John Jiang, ekhumoro's more general-purpose solution is strongly preferable.
54

I'm using:

import sys,os
sys.path.append(os.getcwd())

3 Comments

I often just do sys.path.append('.')
what if the script being runned from a different directory? for example from root directory by giving the full system path? then os.getcwd() will return "/"
Never do this. The current working directory (CWD) is not guaranteed to be what you think it is – especially under unforeseeable edge cases you definitely should have seen coming a mile away. Just reference __file__ instead like any quasi-sane developer.
39

If you don't want to edit each file

  • Install you library like a normal python libray
    or
  • Set PYTHONPATH to your lib

or if you are willing to add a single line to each file, add a import statement at top e.g.

import import_my_lib

keep import_my_lib.py in bin and import_my_lib can correctly set the python path to whatever lib you want

1 Comment

set PYTHONPATH is much more convenient for testing.
23

Create a wrapper module project/bin/lib, which contains this:

import sys, os

sys.path.insert(0, os.path.join(
    os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'lib'))

import mylib

del sys.path[0], sys, os

Then you can replace all the cruft at the top of your scripts with:

#!/usr/bin/python
from lib import mylib

Comments

22

Using python 3.4+

import sys
from pathlib import Path

# As PosixPath
sys.path.append(Path(__file__).parent / "lib")

# Or as str as explained in https://stackoverflow.com/a/32701221/11043825
sys.path.append(str(Path(__file__).parent / "lib"))

1 Comment

Python2 compatible: import pathlib import os sys.path.append(os.path.dirname(file))
20

You can run the script with python -m from the relevant root dir. And pass the "modules path" as argument.

Example: $ python -m module.sub_module.main # Notice there is no '.py' at the end.


Another example:

$ tree  # Given this file structure
.
├── bar
│   ├── __init__.py
│   └── mod.py
└── foo
    ├── __init__.py
    └── main.py

$ cat foo/main.py
from bar.mod import print1
print1()

$ cat bar/mod.py
def print1():
    print('In bar/mod.py')

$ python foo/main.py  # This gives an error
Traceback (most recent call last):
  File "foo/main.py", line 1, in <module>
    from bar.mod import print1
ImportError: No module named bar.mod

$ python -m foo.main  # But this succeeds
In bar/mod.py

2 Comments

Using the m switch is the recommended way of doing this and not the sys path hacks
oh this beats setting the PYTHONPATH environment or the sys.path answers. I have learnt something today - for which I am very, very grateful!
17

If you don't want to change the script content in any ways, prepend the current working directory . to $PYTHONPATH (see example below)

PYTHONPATH=.:$PYTHONPATH alembic revision --autogenerate -m "First revision"

And call it a day!

2 Comments

docs.python.org/3/tutorial/modules.html#the-module-search-path says: " sys.path is initialized from these locations: -The directory containing the input script ". I think this is not true.
I tend to do this, especially in .envrc so that with direnv that is even automatic and isolated as well.
9

This is how I do it many times:

import os
import sys

current_path = os.path.dirname(os.path.abspath(__file__))
sys.path.append(os.path.join(current_path, "lib"))

Comments

5

There is a problem with every answer provided that can be summarized as "just add this magical incantation to the beginning of your script. See what you can do with just a line or two of code." They will not work in every possible situation!

For example, one such magical incantation uses __file__. Unfortunately, if you package your script using cx_Freeze or you are using IDLE, this will result in an exception.

Another such magical incantation uses os.getcwd(). This will only work if you are running your script from the command prompt and the directory containing your script is the current working directory (that is you used the cd command to change into the directory prior to running the script). Eh gods! I hope I do not have to explain why this will not work if your Python script is in the PATH somewhere and you ran it by simply typing the name of your script file.

Fortunately, there is a magical incantation that will work in all the cases I have tested. Unfortunately, the magical incantation is more than just a line or two of code.

import inspect
import os
import sys

# Add script directory to sys.path.
# This is complicated due to the fact that __file__ is not always defined.

def GetScriptDirectory():
    if hasattr(GetScriptDirectory, "dir"):
        return GetScriptDirectory.dir
    module_path = ""
    try:
        # The easy way. Just use __file__.
        # Unfortunately, __file__ is not available when cx_Freeze is used or in IDLE.
        module_path = __file__
    except NameError:
        if len(sys.argv) > 0 and len(sys.argv[0]) > 0 and os.path.isabs(sys.argv[0]):
            module_path = sys.argv[0]
        else:
            module_path = os.path.abspath(inspect.getfile(GetScriptDirectory))
            if not os.path.exists(module_path):
                # If cx_Freeze is used the value of the module_path variable at this point is in the following format.
                # {PathToExeFile}\{NameOfPythonSourceFile}. This makes it necessary to strip off the file name to get the correct
                # path.
                module_path = os.path.dirname(module_path)
    GetScriptDirectory.dir = os.path.dirname(module_path)
    return GetScriptDirectory.dir

sys.path.append(os.path.join(GetScriptDirectory(), "lib"))
print(GetScriptDirectory())
print(sys.path)

As you can see, this is no easy task!

Comments

3

I see a shebang in your example. If you're running your bin scripts as ./bin/foo.py, rather than python ./bin/foo.py, there's an option of using the shebang to change $PYTHONPATH variable.

You can't change environment variables directly in shebangs though, so you'll need a small helper script. Put this python.sh into your bin folder:

#!/usr/bin/env bash
export PYTHONPATH=$PWD/lib
exec "/usr/bin/python" "$@"

And then change the shebang of your ./bin/foo.py to be #!bin/python.sh

Comments

2

I use:

from site import addsitedir

Then, can use any relative directory ! addsitedir('..\lib') ; the two dots implies move (up) one directory first.

Remember that it all depends on what your current working directory your starting from. If C:\Joe\Jen\Becky, then addsitedir('..\lib') imports to your path C:\Joe\Jen\lib

C:\
  |__Joe
      |_ Jen
      |     |_ Becky
      |_ lib

1 Comment

I appreciate the response, but this does not add a path relative to the current running script. It adds a path relative to the current working directory
0

When we try to run python file with path from terminal.

import sys
#For file name
file_name=sys.argv[0]
#For first argument
dir= sys.argv[1]
print("File Name: {}, argument dir: {}".format(file_name, dir)

Save the file (test.py).

Runing system.

Open terminal and go the that dir where is save file. then write

python test.py "/home/saiful/Desktop/bird.jpg"

Hit enter

Output:

File Name: test, Argument dir: /home/saiful/Desktop/bird.jpg

Comments

0

Add these two lines before your module:

import sys,os
sys.path.append(os.pardir)

My file structure as below:

.
├── common
│   ├── __init__.py
│   └── module.py
├── main.py
└── tool
    ├── __init__.py
    └── test.py

and my test code:

import sys,os
sys.path.append(os.pardir)
from common import module

def foo(a,b):
    return module.sum(a,b)

if __name__ == '__main__':
    print(foo(1,2))

So that I can run my code in tool module elegantly in terminal, I use:

(base) robben@Ro tool % python test.py 
3

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.