Python import from another directory: No module named (tested)

ModuleNotFoundError: No module named 'lib' for a folder that's right there in your project almost always comes down to one thing: which directory is first on sys.path. We measured it for every common way of running a script on Python 3.13.5.

1. Why it fails: Python searches the script's folder, not your project

mkdir -p proj/lib proj/scripts && cd proj
echo 'def greet(): return "hello from lib"' > lib/tools.py
cat > scripts/run.py <<'PY'
import sys
print("sys.path[0] =", sys.path[0])
from lib.tools import greet
print(greet())
PY
python3 scripts/run.py 2>&1 | grep -E "^(sys.path|ModuleNotFound|hello)"

Output:

sys.path[0] = /tmp/ad/proj/scripts
ModuleNotFoundError: No module named 'lib'

Measured on Python 3.13.5: when you run python3 scripts/run.py, the first entry on sys.path is the folder containing the script (proj/scripts), not the directory you ran it from. lib/ sits next to scripts/, not inside it, so from lib.tools import greet raises ModuleNotFoundError: No module named 'lib', even though you're standing in proj/.

2. Fix: run it as a module from the project root

cd proj
python3 -m scripts.run

Output:

sys.path[0] = /tmp/ad/proj
hello from lib

Measured: python3 -m scripts.run (dots, no .py) puts the current directory first on sys.path, so lib is found. No code changes and no __init__.py needed on 3.13. You have to run it from proj/.

3. Fix: PYTHONPATH (and why "it works in my IDE")

cd proj
PYTHONPATH=. python3 scripts/run.py
cd scripts && python3 run.py 2>&1 | tail -1

Output:

sys.path[0] = /tmp/ad/proj/scripts
hello from lib
ModuleNotFoundError: No module named 'lib'

Measured: PYTHONPATH=. adds the project root, so the plain script works. It's tied to where you run it from: in scripts/, . isn't the root and it fails again. Most IDEs (VS Code, PyCharm) quietly add the project root to PYTHONPATH for their Run button, which is why the same file can work there and fail in a terminal or cron.

4. Fix: put the path in the script (works from anywhere)

cd proj
cat > scripts/run2.py <<'PY'
import sys, pathlib
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
from lib.tools import greet
print(greet())
PY
python3 scripts/run2.py
cd /tmp && python3 /tmp/ad/proj/scripts/run2.py

Output:

hello from lib
hello from lib

Measured: building the path from __file__ works from any working directory, including /tmp. It's the most robust option for a one-off script. For a real project, make lib an installable package (pip install -e . in a venv) so no path code is needed at all; we didn't measure that one here.

5. The reverse trap: sibling imports break under -m

cd proj
echo 'def helper(): return "sibling ok"' > scripts/helpers.py
echo 'from helpers import helper; print(helper())' > scripts/job.py
python3 scripts/job.py
python3 -m scripts.job 2>&1 | tail -1

Output:

sibling ok
ModuleNotFoundError: No module named 'helpers'

Measured: from helpers import helper works when you run scripts/job.py directly, because scripts/ is sys.path[0], but fails with python3 -m scripts.job, because now the project root is. Pick one way to run your code and write imports for it: from the root, use from scripts.helpers import helper and always use -m. Don't fix it with a relative import (from .helpers): that breaks the direct-run way instead (see our relative import guide).

Frequently asked questions

Why can't Python import a module from another directory?

When you run python3 path/to/script.py, sys.path[0] is the script's folder, not your current directory. We measured sys.path[0] = proj/scripts and No module named 'lib' for a lib/ folder next to scripts/.

How do I import from a parent or sibling directory in Python?

Run from the project root with python3 -m scripts.run, set PYTHONPATH to the root, or insert the root (built from __file__) into sys.path. We measured all three working.

Why does my import work in VS Code or PyCharm but not in the terminal?

IDEs usually add the project root to PYTHONPATH. We measured PYTHONPATH=. making the failing script work, and failing again when run from a different directory.

Is the current working directory on sys.path?

Not when running a script file: we measured sys.path[0] being the script's folder. With python3 -m it is: we measured sys.path[0] being the directory we ran it from.

Why does python -m break my imports?

With -m, sibling files are no longer on sys.path. We measured from helpers import helper working when run directly and failing with -m.

Written with AI assistance. Measured on 2026-10-09 with Python 3.13.5: a script importing from a sibling folder run directly, with python3 -m, with PYTHONPATH from two directories, with a __file__-based sys.path insert from two directories, and a sibling import under both run styles. Every block was run verbatim and its output pasted from that run. PlainHeaders is our extension; everything else here is built-in Python.