"How do I import a Python file from another folder" is a top-5 Python question on Stack Overflow with 2,600+ votes, and most answers focus on tricks (sys.path hacks, PYTHONPATH, hardcoded paths) that work in the moment and bite you later. The real fix is a one-time setup that makes imports just work from then on: pick a layout (src or flat), make the right folders into packages with __init__.py, install your project in editable mode, and use absolute imports. This guide walks the five real import methods with a decision rule for each, explains the src vs flat layout debate, and covers the script-vs-module distinction that breaks half of "but my import works in the REPL" bug reports. It's one of 10 explainers in our Python Environment Setup pillar page.
Key Takeaways
Editable install is the modern default.pip install -e . at the project root makes import myproject.x work everywhere, no sys.path hacks.
src layout for libraries; flat for scripts. The src layout catches packaging bugs early. Flat is fine for a one-file utility.
__init__.py marks a folder as a package. Empty is fine. Without it, Python uses namespace packages, which work but lose initialization control.
Absolute imports beat relative imports for cross-package. Relative imports (from .utils import x) are fine within one package.
Run code with python -m mypackage.module, not python mypackage/module.py. The -m form sets up imports correctly; the file path form is the source of "works in REPL, fails as script" bugs.
Same project content, two layouts. Modern Python defaults to src layout for anything bigger than a one-file script.
What Counts as a Package
Python has two kinds of folders that work as packages:
Regular package. A folder containing an __init__.py file (the file can be empty, can hold initialization code, can re-export names). Python runs __init__.py when the package is first imported.
Namespace package. A folder with NO __init__.py. Since Python 3.3 (PEP 420), Python treats this folder as an implicit namespace package. It works for simple cases but you can't put initialization code or package-level constants in it.
For 95% of projects, you want regular packages. Add an empty __init__.py to every folder you intend to import from, commit it, move on. The exception is when you genuinely want to split a single import namespace across multiple installable packages, which is rare unless you're building plugins or a framework.
# Confirm a folder is importable:
ls myproject/utils/
# __init__.py helpers.py validators.py
# From the project root:
python -c "from myproject.utils import helpers; print(helpers.__file__)"
# Should print the path to helpers.py
myproject/
|- pyproject.toml
|- README.md
|- tests/
| |- test_main.py
|- myproject/ # the package, at the project root
|- __init__.py
|- main.py
|- utils.py
Strengths. Smallest possible setup. Imports work without installing anything because the project root is on sys.path when you run python -m myproject.main from there.
Weaknesses. The project root is on sys.path, so pyproject.toml and README.md are technically importable as modules (they aren't Python, so nothing happens, but the namespace is dirty). More importantly, your tests can accidentally import the local myproject folder instead of the installed version, masking packaging bugs that show up only after users install the package.
Strengths. The source isn't on sys.path unless you explicitly install it. That forces pip install -e . as a one-time setup step, which means your tests import the installed package, not the local source. Packaging bugs (missing files in the wheel, wrong entry points) get caught the first time you run tests instead of after the wheel ships.
Weaknesses. One more directory level. You have to run pip install -e . at least once before anything imports.
The recommendation: use src layout for anything you'll publish to PyPI, anything bigger than a one-file script, and anything where tests matter. Use flat layout for quick utilities, demos, and short scripts.
Editable Install: The One-Command Setup
The modern answer to "how do I import my own code from anywhere" is pip install -e .:
# Once, in your project root:
pip install -e .
# After that, from ANY directory:
python -c "import myproject; print(myproject.__file__)"
The -e flag (editable mode) tells pip to install your package but make changes to the source take effect immediately. Pip writes a small .pth file into site-packages that points back at your source directory; Python adds that path to sys.path at startup; your imports work everywhere.
For this to work you need a minimal pyproject.toml:
[project]
name = "myproject"
version = "0.1.0"
dependencies = [
"requests",
]
[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"
# For src layout, also:
[tool.setuptools.packages.find]
where = ["src"]
That's enough. Run pip install -e . once. Edit your code, rerun your scripts, no more sys.path hacking.
If pip install -e . hits the externally-managed-environment error on Linux or macOS, you're trying to install into the system Python. Make a venv first; see our PEP 668 guide.
The Five Import Methods (With a Decision Rule)
Method 1: Absolute Imports
The default. Name the full path from the top of your package:
from myproject.utils import helpers
from myproject.core.parser import parse_csv
import myproject.config
When right: always, for cross-package imports and most within-package imports too. Required after pip install -e . for clean import behavior.
Method 2: Relative Imports
Dots indicate "up the tree" from the current module:
# Inside myproject/core/parser.py:
from . import helpers # same package: myproject/core/helpers.py
from .. import config # parent package: myproject/config.py
from ..utils.validators import valid_email # sibling subpackage
When right: within a package, especially when you may want to rename or move a subtree later. The relative path adapts automatically. Discouraged for cross-package imports (PEP 328 originally pushed absolute imports as default; relative remains valid for in-package).
When right: almost never in production code. Legitimate cases are a Jupyter notebook prototyping against a sibling project, or a one-off script that must run without installation. The hardcoded path is the problem: change the project location and the import breaks.
When right: almost never as a fix for missing imports. Legitimate uses are integrating Python into another build system (Bazel, certain Docker setups) or running scripts in a controlled environment where everyone shares the same PYTHONPATH. As a quick fix, it leaks: every Python process you run inherits it, which causes confusing imports much later.
Method 5: Editable Install (pip install -e .)
Covered above. This is the right answer for any project bigger than a single script.
Decision Rule
Situation
Method
Cross-package import
Absolute (after pip install -e .)
Sibling module within the same package
Absolute OR relative; pick a style and stick to it
Moving a subtree, want imports to follow
Relative within the subtree
Jupyter notebook prototyping against a sibling repo
sys.path.insert at top of notebook
One-off script, can't install
sys.path.insert with relative path
Integrating with another build system
PYTHONPATH
Anything more than a single script
pip install -e .
The Script-vs-Module Trap
Half of "but my import worked in the REPL" reports come from one mistake: running a Python file as a script when you should run it as a module.
# Script form: WRONG when the file is part of a package
python myproject/main.py
# Adds myproject/ to sys.path, NOT the project root.
# from myproject.utils import x → ModuleNotFoundError
# Module form: RIGHT when the file is part of a package
python -m myproject.main
# Adds the CURRENT DIRECTORY to sys.path, then imports myproject.main.
# Imports work as you'd expect.
The difference is where sys.path[0] ends up. When you run a file by path, Python puts that file's directory on the path. When you use -m, Python puts the current directory on the path AND treats the named module as part of the package hierarchy.
This is also why if __name__ == "__main__" matters: it's the marker for "this file can be run directly." See our existing explainer What Does if __name__ == "__main__": Do? for the full story.
Entry Points: Run Your Package Like a Command
For projects bigger than a single file, declare an entry point in pyproject.toml:
The myproject command becomes available on PATH; it calls myproject.main.cli() as the entry point. No more "python -m" gymnastics; no script files to find. The same mechanism is how black, ruff, pytest, and every other Python CLI tool ends up on your PATH.
Diagnostic: Why Won't My Import Work?
Four questions, in order:
Is the folder a package? Look for __init__.py. If missing, add an empty one (or use namespace packages deliberately).
Is the package on sys.path? Run python -c "import sys; print('\n'.join(sys.path))" and check that the parent of your top-level package directory is on the list. If not, you need pip install -e . or you're running from the wrong directory.
Are you running the file as a script or a module? Use python -m myproject.main, not python myproject/main.py, when the file is part of a package.
Are you in the right venv? Different venv = different installed packages. See our ModuleNotFoundError fix guide for the full diagnostic.
Common Antipatterns
Hardcoded sys.path Hacks at the Top of Every File
# DON'T do this:
import sys, os
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
from myproject.utils import x
This works while you run code from the project root. It breaks when you ship the package, when CI runs it, when someone clones into a different directory. pip install -e . solves the same problem permanently.
Trying to Import a Sibling Script by Name
# Project root:
# main.py
# utils.py
# In main.py:
import utils # works when you run from project root
# BREAKS when you run from anywhere else
Either move both files into a package directory (with __init__.py) and use absolute imports, or use the script form with explicit sys.path setup. The lazy version above is brittle.
Using both __init__.py and Namespace Packages in the Same Tree
If some folders have __init__.py and some don't, Python's import resolution gets confusing fast. Pick one approach for the whole project. For 99% of cases that means "every folder I want to import from has an empty __init__.py."
Real-World Cookbook
Project with src layout, one venv, one editable install
uv's --lib flag picks src layout. uv sync includes editable install of the local project.
Importing tests from the project root
Put tests/ at the project root (not under src/). Tests should import the installed package, which means from myproject.utils import x, never from src.myproject.utils import x. The src layout enforces this for you.
Frequently Asked Questions
How do I import a Python file from another folder?
Make the other folder a package by adding an __init__.py file (can be empty), then import with from folder_name import module_name. That works when you run your code from the project root. For larger projects, organize your code in a src layout (src/myproject/) and run pip install -e . at the project root once; after that, import myproject.module works from anywhere. Avoid sys.path.insert and PYTHONPATH for routine imports; they create hidden coupling between your code and the environment, which breaks the moment your script runs from a different directory.
What does __init__.py do?
It marks a folder as a Python package. Without __init__.py, Python (since 3.3) treats the folder as an implicit namespace package, which works for simple cases but doesn't carry sub-package state or initialization code. With __init__.py (even empty), the folder is a regular package: Python runs the file when the package is first imported, and you can put package-level imports, version strings, and __all__ lists in it. For most projects, an empty __init__.py in every package folder is the right answer.
What is the src layout in Python?
The src layout places your package code in a top-level src/ directory: src/myproject/__init__.py instead of myproject/__init__.py. The benefit is that you can't accidentally import your code without installing it (pip install -e .) because the source isn't on sys.path by default. That catches a whole class of packaging bug: if your tests pass with src layout, they'll pass for end users after pip install. The flat layout (package at the project root) is fine for scripts and small projects but invites surprises in larger codebases.
What is the difference between relative and absolute imports in Python?
Absolute imports name the full path from the top of the package: from myproject.utils import helper. Relative imports name a path from the current module: from .utils import helper (one dot = current package) or from ..core import helper (two dots = parent package). PEP 328 (2003) recommended absolute imports as the default. Relative imports are fine within a package and good for moving subtrees around without rewriting imports. Cross-package imports should always be absolute.
What does pip install -e . do?
It installs your package into the active Python in editable mode: a .pth file in site-packages points back at your source directory. Changes to your code take effect immediately without reinstalling, but Python's import machinery treats the package as if it were installed normally, so import myproject works from anywhere and the package gets all the benefits of a real install (entry points, package metadata, dependency resolution). This is the modern default for any project bigger than a single script.
The Bottom Line: src Layout, Editable Install, Absolute Imports
One-time setup, lifetime payoff. Use src layout for libraries; flat for one-file scripts. Add empty __init__.py to every folder you import from. Run pip install -e . once, then import myproject.x works from anywhere. Use absolute imports for cross-package; relative is fine within. Run code with python -m myproject.main, not the file path. Skip sys.path hacks and PYTHONPATH unless you have a specific reason. Those rules cover 95% of import problems before they happen. For the rest, browse the Python Environment Setup pillar page, jump to ModuleNotFoundError fix, or check venv decision guide for the wider setup.
Practice Imports the Right Way
CodeGym's Python track teaches packages, imports, and project layout through 800+ hands-on tasks across 62 levels. The AI validator catches import structure bugs in seconds; the AI mentor explains what broke when it does. First level free; full plan on the pricing page.
Begin your Python learning path →
GO TO FULL VERSION