I'm quite a beginner in Python and I found the answer of Anand was very good but quite complicated to me, so I try to reformulate :

1) insert and append methods are not specific to sys.path and as in other languages they add an item into a list or array and :
* append(item) add item to the end of the list,
* insert(n, item) inserts the item at the nth position in the list (0 at the beginning, 1 after the first element, etc ...).

2) As Anand said, python search the import files in each directory of the path in the order of the path, so :
* If you have no file name collisions, the order of the path has no impact,
* If you look after a function already defined in the path and you use append to add your path, you will not get your function but the predefined one.

But I think that it is better to use append and not insert to not overload the standard behaviour of Python, and use non-ambiguous names for your files and methods.

Answer from herve-guerin on Stack Overflow
Top answer
1 of 2
96

I'm quite a beginner in Python and I found the answer of Anand was very good but quite complicated to me, so I try to reformulate :

1) insert and append methods are not specific to sys.path and as in other languages they add an item into a list or array and :
* append(item) add item to the end of the list,
* insert(n, item) inserts the item at the nth position in the list (0 at the beginning, 1 after the first element, etc ...).

2) As Anand said, python search the import files in each directory of the path in the order of the path, so :
* If you have no file name collisions, the order of the path has no impact,
* If you look after a function already defined in the path and you use append to add your path, you will not get your function but the predefined one.

But I think that it is better to use append and not insert to not overload the standard behaviour of Python, and use non-ambiguous names for your files and methods.

2 of 2
65

Because Python checks in the directories in sequential order, starting at the first directory in the sys.path list, until it finds the .py file it was looking for.

Normally, the current directory or the directory of the script is the first element in the list, unless you modify it (like you did). From documentation:

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. If the script directory is not available (e.g. if the interpreter is invoked interactively or if the script is read from standard input), path[0] is the empty string, which directs Python to search modules in the current directory first. Notice that the script directory is inserted before the entries inserted as a result of PYTHONPATH.

So, most probably, you had a .py file with the same name as the module you were trying to import from, in the current directory (where the script was being run from).

Also, something to note about ImportErrors - Let's say the import error says: ImportError: No module named main. This doesn't mean main.py is overwritten - if it was overwritten it would still exist and we would not have issues trying to read it. It's some module above this that got overwritten with a .py or some other file.

For example, consider a directory structure that looks like this:

 - test
    - shared
       - __init__.py
       - phtest.py
    - testmain.py

Now from testmain.py, I call from shared import phtest, which works fine.

Now let's say I introduce a shared.py in test directory:

 - test
    - shared
       - __init__.py
       - phtest.py
    - testmain.py 
    - shared.py

Now when I try to do from shared import phtest from testmain.py, I get the error:

ImportError: cannot import name 'phtest'

As you can see above, the file that introduced the issue is shared.py, not phtest.py.

Top answer
1 of 3
171

How python finds its modules

Strictly taken, a module is a single python file, while a package is a folder containing python files, accompanied by a (can be empty) file named __init__.py, to tell python it is a package to import modules from. In both cases, modules need their .py extension, but importing them is done without (see further below).

By default, Python looks for its modules and packages in $PYTHONPATH.

To find out what is included in $PYTHONPATH, run the following code in python (3):

import sys
print(sys.path)

How to add a directory

Occasionally

From within a python file, you can add path(s) occasionally to the default path by adding the following lines in the head section of your python application or script:

import sys
sys.path.insert(0, "/path/to/your/package_or_module")

For example:

if I have a folder: /home/myname/pythonfiles, and I want to import the file module_1.py, located in that directory, I add this to the head section of my code:

import sys
sys.path.insert(0, "/home/myname/pythonfiles")

And I can simply import the file module_1.py by:

import module_1

When I create a package and want to import module(s) from the package, I need to create a folder in $PYTHONPATH, containing the modules, accompanied by a (can be empty) file called __init__.py

For example:

To import from a package (folder) called my_package in /home/myname/pythonfiles , add the /home/myname/pythonfiles path to your $PYTHONPATH, like in example 1, and import the module called module_2.py (inside the package folder) simply with: `

from <packagename> import module_2

Adding directories to $PYTHONPATH permanently:

Add the following line to your ~/.profile file.

export PYTHONPATH=$PYTHONPATH:/path/you/want/to/add

Subdirectories

From within a package, subdirectories are not included just like that; you need to "chain" the directories. To import a module module_3.py, inside folder subfolder inside folder packagename:

import packagename.subfolder.module_3

Given the fact that all subfolders in the package include their own __init__.py file.

When a module is in the same directory as the script or application

There is no need to insert the path to a module when it is in the same directory as the script or application, it is automatically added.

Example:

If I have a folder, containing script.py and module.py, I can simply import the module by:

import module
2 of 3
7

The correct way to use relative import is:

from ..Common import foo1

And you also need a __init__.py in all your folders.

Discussions

python - Why use sys.path.append(path) instead of sys.path.insert(1, path)? - Stack Overflow
Edit: based on a Ulf Rompe's comment, it is important you use "1" instead of "0", otherwise you will break sys.path. I have been doing python for quite a while now (over a year), and I am always More on stackoverflow.com
🌐 stackoverflow.com
what does sys.path.append(".") do?
sys.pathis a list of directories where the Python interpreter will search for modules/files when import is invoked. sys.path.append(".") tells Python to append "this current directory" (that's the dot) to the list of places it should look for files. More on reddit.com
🌐 r/learnpython
4
4
May 1, 2020
Python files included by `sys.path.insert()` are missing on the final executable
I have this lines on my code: import sys sys.path.insert(0,'../python_debug') import debug_tools So, after compiling the executable .exe the program closed with missing module error: Traceb... More on github.com
🌐 github.com
June 6, 2017
Why is it not recommended to use sys.path.append("") for packages that aren't in your path?
Is there not a way to just have them all go to one place and my Python projects go there to get them? Yes, that's exactly what the pip command does. More on reddit.com
🌐 r/learnpython
11
6
February 16, 2022
🌐
GeeksforGeeks
geeksforgeeks.org › python › sys-path-in-python
sys.path in Python - GeeksforGeeks
August 21, 2024 - When a module(a module is a python file) is imported within a Python file, the interpreter first searches for the specified module among its built-in modules. If not found it looks through the list of directories(a directory is a folder that contains related modules) defined by sys.path.
Top answer
1 of 3
55

If you really need to use sys.path.insert, consider leaving sys.path[0] as it is:

sys.path.insert(1, path_to_dev_pyworkbooks)

This could be important since 3rd party code may rely on sys.path documentation conformance:

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.

2 of 3
51

If you have multiple versions of a package / module, you need to be using virtualenv (emphasis mine):

virtualenv is a tool to create isolated Python environments.

The basic problem being addressed is one of dependencies and versions, and indirectly permissions. Imagine you have an application that needs version 1 of LibFoo, but another application requires version 2. How can you use both these applications? If you install everything into /usr/lib/python2.7/site-packages (or whatever your platform’s standard location is), it’s easy to end up in a situation where you unintentionally upgrade an application that shouldn’t be upgraded.

Or more generally, what if you want to install an application and leave it be? If an application works, any change in its libraries or the versions of those libraries can break the application.

Also, what if you can’t install packages into the global site-packages directory? For instance, on a shared host.

In all these cases, virtualenv can help you. It creates an environment that has its own installation directories, that doesn’t share libraries with other virtualenv environments (and optionally doesn’t access the globally installed libraries either).

That's why people consider insert(0, to be wrong -- it's an incomplete, stopgap solution to the problem of managing multiple environments.

🌐
O'Reilly
oreilly.com › library › view › arcpy-and-arcgis › 9781783988662 › ch02s06.html
The sys.path.append() method - ArcPy and ArcGIS
February 26, 2015 - The sys.path function is a list ... the user wants to import. To avoid the need to adjust sys.path, copy the module into the site-packages folder....
Author: Silas Toms
Published: 2015
Pages: 224
🌐
YouTube
youtube.com › watch
PYTHON : Effect of using sys.path.insert(0, path) and sys.path(append) when loading modules - YouTube
PYTHON : Effect of using sys.path.insert(0, path) and sys.path(append) when loading modules [ Gift : Animated Search Engine : https://www.hows.tech/p/recomme...
Published: December 8, 2021
Find elsewhere
🌐
Linux Hint
linuxhint.com › sys-path-append-python
How to Use Sys.path.append() in Python – Linux Hint
When we append the path using the sys.path.append() method, we first append the location of the module to Path, and then import it. If done in such a manner, the Python interpreter should be able to locate the requested module, and therefore retrieve the secret variable.
🌐
YouTube
youtube.com › watch
PYTHON : Why use sys.path.append(path) instead of sys.path.insert(1, path)? - YouTube
PYTHON : Why use sys.path.append(path) instead of sys.path.insert(1, path)? [ Gift : Animated Search Engine : https://www.hows.tech/p/recommended.html ] PYT...
Published: December 6, 2021
🌐
GitHub
gist.github.com › justinmklam › c453ea62b47f4e302dbd83c05882e3bd
Techniques for python path manipulation and relative imports in (i.e. when not dealing with a package). · GitHub
python-paths-and-imports.md · ....path.join(current_dir, "..")) For simple project structures: import sys, os sys.path.insert(1, os.path.join(sys.path[0], '..')) When absolute paths are required: import os,sys,inspect currentdir ...
🌐
Clinic Software
clinicsoftware.com › syspathinsert
Sys.path.insert - ClinicSoftware.com
Sys.path.insert allows developers to specify custom directories where Python looks for modules.
🌐
Reddit
reddit.com › r/learnpython › what does sys.path.append(".") do?
r/learnpython on Reddit: what does sys.path.append(".") do?
May 1, 2020 -

I have a structure like this:

Parent_Folder
-- foo.py
    Child_Folder_1
    -- bar.py
    -- egg.py
    Child_Folder_2
    -- spam.py

I tried relative importing on bar.py,

from ..import foo

but it did not work for reasons, and I somehow found a way to do this via internet.

import sys
sys.path.append(".")

now I can import foo.py and egg.py and even spam.py on bar.py, but I wonder how this is working, and what did that single line do. I've searched the web, but I doubt I found any understandable answers, so I'm posting this question here.

Since I'm not a professional at neither python nor programming, a kind explaination would be welcome :D Anyway, thanks for your time for reading this.

🌐
GitHub
github.com › pyinstaller › pyinstaller › issues › 2640
Python files included by `sys.path.insert()` are missing on the final executable · Issue #2640 · pyinstaller/pyinstaller
June 6, 2017 - I have this lines on my code: import sys sys.path.insert(0,'../python_debug') import debug_tools So, after compiling the executable .exe the program closed with missing module error: Traceb...
🌐
Reddit
reddit.com › r/learnpython › why is it not recommended to use sys.path.append("") for packages that aren't in your path?
r/learnpython on Reddit: Why is it not recommended to use sys.path.append("") for packages that aren't in your path?
February 16, 2022 -

Two things,

First, I used it to try to find the Tensorflow package that wasn't in my sys.path and be able to use it for my PyCharm project.

Second, it didn't work, and someone told me it's not recommended to do that.

This whole path, and packages thing is overwhelmingly confusing.

Is there not a way to just have them all go to one place and my Python projects go there to get them?

Thank you

Top answer
1 of 5
24

The pythonic solution for the import problem is to not add sys.path (or indirectly PYTHONPATH) hacks to any file that could potentially serve as top-level script (incl. unit tests), since this is what makes your code base difficult to change and maintain. Assume you have to reorganize your project structure or rename folders.

Instead this is what editable installs are made for. They can be achieved in 2 ways:

  1. pip install --editable <path> (requires a basic setup.py)
  2. conda develop <path> (requires the conda-build package)

Either way will add a symlink into your site-packages folder and make your local project behave as if it was fully installed while at the same time you can continue editing.

Always remember: KEEP THINGS EASY TO CHANGE

2 of 5
10

Pythonic Solution: Use packages and modules as they are intended to be used

Ask yourself, why did you want to create a src directory?

I would suggest that more than likely you wanted to follow a convention you knew from another language. (Maybe Java, maybe C, C++, or something else.)

However, if you use Python packages in the way they are intended to be used, there is a far simpler solution.

First lets review a few key points.

  • To run the Python file main.py, you run it using the Python interpreter like so: python3 main.py.
  • When the Python interpreter starts, it adds the current working directory to its path. (via sys.path)
  • It will also add the directory containing the module to be run to its path
  • More information can be found in this documentation page: https://docs.python.org/3/tutorial/modules.html
  • One issue with that documentation page is it explains how to modify sys.path from within Python code. The issue with that is it gives developers the idea that this is possible and therefore should be used as a solution to import and path problems when it should not.
  • The Python interpreter will search the sys.path and PYTHONPATH directories list for modules and packages to resolve when it sees an import statement
  • A Python package is a directory with an __init__.py file
  • A Python module is just a regular Python file
  • The __init__.py file is there to signal to the Python interpreter that it needs to recursively search subdirectories for more Python packages and modules. This is why an __init__.py is usually empty.
  • Without an __init__.py the Python interpreter will simply ignore a directory
  • This rule is actually an optmization to prevent the interpreter from becoming slow to start up if there are a large number of subdirectories and files to search
  • From this we conclude that all local source code should be resolvable from the same directory as the one used to run the target module (main.py)

With that information, you can re-structure your project:

sample_project/
    my_python_package/
        __init__.py
        sub_package_1/
            __init__.py
            utils.py
        sub_package_2/
            __init__.py 
            models.py
    app.py

Run app.py from the directory sample_project: python3 app.py

You can actually go further. If your project becomes very large, it sometimes makes sense to run modules within packages using python3 -m some_package.some_module. Then everything, including app.py becomes a package. I don't think you need this in this particular case, but if you have large numbers of "executable" Python files which are better grouped into a set of directories, then this is the approach to take.

Note that:

  • This solution is simple (bordering on trivial, if not necessarily that obvious)
  • There is no src directory. Forget about src. This works well in other languages, it doesn't fit into the Python model for how a project should be structured
  • You did not need to modify PYTHONPATH
  • You did not need to modify sys.path
  • You did not need to write extra code to be able to resolve imports
  • This solution is easy to understand, it is straight forward and has minimal complexity

An experiment to learn about PYTHONPATH and sys.path

You can find out what PYTHONPATH and sys.path are set to with a short experimental code:

$ cd ~
$ mkdir python-path-test
$ touch python-path-test/main.py 
# main.py

import os
import sys

print(f'PYTHONPATH:')
for string in os.environ.get('PYTHONPATH').split(';'):
    print(string)

print(f'sys.path:')
for string in sys.path:
    print(string)
$ export PYTHONPATH=`pwd`
$ python3 python-path-test/main.py
PYTHONPATH:
/home/username
sys.path:
/home/username
/home/username/python-path-test
/usr/lib/python311.zip
/usr/lib/python3.11
/usr/lib/python3.11/lib-dynload
/usr/local/lib/python3.11/dist-packages
/usr/lib/python3/dist-packages
/usr/lib/python3.11/dist-packages

Further explanation in regards to other answers

Let me address the issues with the other answers here. All of the answers provided will work, but none of them take the simplest and "most obviously correct" approach.

The reason for this is the "most obviously correct" approach is not that obvious, especially if you come to Python from other languages where things work differently.

Just to say as well - it took me a long time to figure out the solution to the exact same problem which is shown in the question and I only figured out the solution when I went to work for a firm where someone else had figured this out before me.

Also: None of this is really explained on any documentation page anywhere, so it is hardly surprising that most people get it wrong, or do something unneccessarily complex when it isn't needed.

Overview of other solutions:

So far several other solutions have been proposed:

  1. Use setuptools and virtual environments to manage what is known as an "editable install".

I don't like this for two reasons: It is more work than is necessary, and you are pretending that some local source code is a PIP package, when it isn't. It just seems like a bizzare thing to do. (This is exactly what I used to do before realizing there is an easier way.)

  1. Write Python code to modify the sys.path or PYTHONPATH environment variable

I don't like this because it is a hack:

  • The PYTHONPATH environment variable is intended to be used to store the locations of installed packages on your system
  • It should be a semi-permenant thing which doesn't change (often)
  • This is similarly the case with sys.path
  • The other reason modifying PYTHONPATH is bad is because you are embedding (hiding) some code within your project which does unexpected things
  • PYTHONPATH should be managed by the Operating System, or at least by the user in a shell
  • In my experience, twiddling things which should be managed by your operating system from within code frequently leads to hard to find bugs and hard to understand code
  1. Modify PYTHONPATH from a shell

This is better than the above proposal of modifying it from with Python code, but it just isn't necessary, for the reasons I explained above.


Appendix:

To give a little further helpful information. Some languages (and corresponding build tools) are designed with maximum flexibility. Others contain built-in rules which constrain how files and folders should be arranged for the build system to work property. These rules are not always explicit or obvious.

cmake is a good example of a build system which offers maximum flexibility. Many projects contain a src directory, under which all the C/C++ code lives. The reason for this is cmake facilitates using explicit and arbitrary paths to configure the build.

On the other hand, the Rust module system is much more constrained. The existence of a directory "creates" a module (or submodule). Cargo and Rust require you to use the filesystem in a constrained way to get the modular structure you want.

Julia is more similar to C++ in that modules are explicit - there is a module keyword, and this is the only way to create a module. It also has include which can take an arbitrary path - although using the Julia build system in an arbitrary way is not recommended, just as it would not be recommended with cmake.

Finally, Python is a bit more tricky. Similarly to Julia, the build system needs to be told how and where modules can be loaded from. It is generally better not to add lots of arbitrary hard-coded paths to the code or build system. Rather, avoiding this and working with what the language offers natively is preferable.

In both the case of Julia and Python, this means that the interpreter/runtime should be able to load your code without adding additional paths.

With this constraint, you will write a much simpler project structure.

To answer some questions in the comments

Here is what one of my Python projects looks like.

python_project_root_directory/
  .vscode/
    settings.json
  .venv/
  lib_something/
    __init__.py
    lib_something_files.py
  the_main_module
    __main__.py
    __init__.py # might not be requried
    main.py # called from __main__.py
  tests/
    some_group/
      test_something.py
      test_another_thing.py
    another_group/
      test_more_things.py
  Dockerfile

Dockerfile

Note: Does not use .venv, because a Docker container is its own isolated environment. You can use a .venv if you want. Change the command to cmd ["./.venv/bin/python3", "-m", "the_main_module"].

from python:3.12-bookworm
... other stuff ...
run pip3 install --no-cache-dir --upgrade -r requirements.txt
cmd ["python3", "-m", "the_main_module"]

If rather than wanting to run a main module, you want to run a python file as "main", change to cmd ["python3", "main.py"].

settings.json

{
    "python.testing.pytestArgs": [
        "tests"
    ],
    "python.testing.unittestEnabled": false,
    "python.testing.pytestEnabled": true,
}
🌐
Reddit
reddit.com › r/learnpython › why does sys.path.insert change the working directory and how to make it stop?
r/learnpython on Reddit: Why does sys.path.insert change the working directory and how to make it stop?
May 31, 2018 -

I do sys.path.insert(0, '/other/dir') in order to import modules from directories which are not in subdirectories of the current project/far away but it is changing the working directory to that directory.

I never noticed that behavior tile recently.

I prefer to do insert rather than installing modules for system wide imports because I tried that once and since I often am changing the code in those modules I found it a nuisance to constantly be having to re-install the module every time I made a change. And that is for only one. It would really get annoying if there were several files which would need reinstalling every time the slightest change was made.

I am currently having to change the working directory back to the origininal one again with os but why does it do it in the first place and how to prevent?

I am often importing modules from far away and unrelated(to the original script) places so want to know how to do this with least fuss- the advised make them installable suggestion was way more trouble though due to aforementioned reason.

🌐
How to Use Linux
howtouselinux.com › home › understanding sys.path in python
understanding sys.path in Python - howtouselinux
October 9, 2025 - Here are the commonly used variables and functions in sys module. argv: stores the command-line arguments passed to a Python script. args: a tuple of strings that contains the positional arguments passed to a function. exitcode: indicates the status code of the program. executable_path: returns the path of the Python executable file.
🌐
Python
docs.python.org › 3 › library › sys_path_init.html
The initialization of the sys.path module search path — Python 3.15.2 documentation
To completely override sys.path create a ._pth file with the same name as the shared library or executable (python._pth or python311._pth). The shared library path is always known on Windows, however it may not be available on other platforms. In the ._pth file specify one line for each path to add to sys.path.
🌐
Safjan
safjan.com › home › note › adding parent directory to python path
Adding Parent Directory to Python Path - Krystian Safjan's Blog
May 28, 2024 - To add a parent directory to the Python path, you can use the following code: ... This code imports the sys module and then uses the insert() method to add the parent directory (indicated by '..') to the beginning of the sys.path list.
🌐
Johnny-lin
johnny-lin.com › cdat_tips › tips_pylang › path.html
Appending to Your Python Path - Johnny Lin
Your path (i.e. the list of directories Python goes through to search for modules and files) is stored in the path attribute of the sys module.