143

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.

dreftymac
  • 31,404
  • 26
  • 119
  • 182
James Harr
  • 1,787
  • 2
  • 12
  • 10
  • 2
    See also: http://stackoverflow.com/questions/2349991/python-how-to-import-other-python-files/20749411#20749411 – dreftymac May 18 '16 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. – KayCee Dec 06 '21 at 15:25

12 Answers12

157

This is what I use:

import os, sys
sys.path.append(os.path.join(os.path.dirname(__file__), "lib"))
jterrace
  • 64,866
  • 22
  • 157
  • 202
  • 9
    I would do sys.path.insert(0, ..) so that it doesn't get overridden by other paths. – John Jiang Feb 08 '19 at 19:04
  • 1
    Is there really no way to get this to run automatically? – Denis de Bernardy Jul 08 '19 at 11:15
  • 2
    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](https://stackoverflow.com/users/2930156/john-jiang), [ekhumoro](https://stackoverflow.com/users/984421/ekhumoro)'s [more general-purpose solution](https://stackoverflow.com/a/8663557/2809027) is strongly preferable. – Cecil Curry Oct 16 '19 at 06:02
43

I'm using:

import sys,os
sys.path.append(os.getcwd())
DusX
  • 551
  • 4
  • 5
  • 9
    I often just do `sys.path.append('.')` – kimbo May 03 '19 at 18:06
  • 5
    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 "/" – obayhan Oct 12 '19 at 12:14
  • 17
    **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. – Cecil Curry Oct 16 '19 at 04:55
35

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

Anurag Uniyal
  • 85,954
  • 40
  • 175
  • 219
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
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
20

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"))
GollyJer
  • 23,857
  • 16
  • 106
  • 174
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!

Khanh Hua
  • 1,086
  • 1
  • 14
  • 22
  • https://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. –  May 14 '17 at 19:27
  • I tend to do this, especially in .envrc so that with direnv that is even automatic and isolated as well. – EdvardM Jul 11 '19 at 13:41
16

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
Eyal Levin
  • 16,271
  • 6
  • 66
  • 56
  • 8
    Using the m switch is the recommended way of doing this and not the sys path hacks – Mr_and_Mrs_D May 28 '20 at 17:24
  • oh this beats setting the PYTHONPATH environment or the sys.path answers. I have learnt something today - for which I am very, very grateful! – KayCee Dec 06 '21 at 14:50
6

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"))
gildniy
  • 3,528
  • 1
  • 33
  • 23
4

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!

Moot
  • 2,195
  • 2
  • 17
  • 14
Benilda Key
  • 2,836
  • 1
  • 22
  • 34
1

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
  • 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 – James Harr Aug 29 '20 at 20:44
0

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

imbolc
  • 1,620
  • 1
  • 19
  • 32
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