One of the most confusing aspects of Python for newcomers - and even experienced developers - is the import system. You've probably seen those cryptic ImportError: attempted relative import with no known parent package
messages and wondered what you did wrong.
The truth is, Python's import system is elegant once you understand it. Let's demystify absolute imports, relative imports, and project structure best practices.
Before diving into imports, let's talk about why a good project structure is crucial:
Here's a well-organized project structure we'll use as reference:
my_project/
├── __init__.py # Makes my_project a package
├── main.py # Entry point
├── requirements.txt # Dependencies
├── README.md
├── setup.py # For packaging (optional)
├── package_a/
│ ├── __init__.py
│ ├── module_a1.py
│ └── module_a2.py
└── package_b/
├── __init__.py
├── module_b1.py
└── subpackage_c/
├── __init__.py
└── module_c1.py
__init__.py
That __init__.py
file is crucial. Its presence tells Python: "This directory is a package, not just a folder."
The file can be empty, but it can also:
__all__
for from package import *
behaviorExample __init__.py
:
# my_project/package_a/__init__.py
# Import frequently used functions into package namespace
from .module_a1 import greet_a1
from .module_a2 import extended_greet
# Package-level constant
VERSION = "1.0.0"
# Control what * imports
__all__ = ['greet_a1', 'extended_greet', 'VERSION']
Now you can do:
from package_a import greet_a1 # Instead of package_a.module_a1.greet_a1
When you write import something
, Python searches in this order:
os
, sys
)sys.path
:
PYTHONPATH
environment variableYou can see your path:
import sys
print(sys.path)
Absolute imports specify the full path from your project root or from sys.path
.
my_project/package_a/module_a1.py
:
def greet_a1():
print("Hello from module_a1!")
class HelperClass:
def __init__(self, name):
self.name = name
my_project/package_b/module_b1.py
:
def perform_task():
print("Performing task in module_b1!")
my_project/main.py
:
# Absolute imports - clear and explicit
from package_a import module_a1
from package_b import module_b1
def run():
print("Running main application...")
module_a1.greet_a1()
module_b1.perform_task()
helper = module_a1.HelperClass("Tashif")
print(f"Helper name: {helper.name}")
if __name__ == "__main__":
run()
From the directory above my_project
:
python my_project/main.py
Or from inside my_project
:
python main.py
✅ Crystal clear - No ambiguity about where modules come from
✅ Safe refactoring - Renaming parent packages won't break imports
✅ IDE friendly - Better autocomplete and navigation
✅ Explicit is better than implicit - The Zen of Python
Relative imports use dots (.
) to navigate the package hierarchy, like Unix paths:
.
= current package..
= parent package...
= grandparent packageRelative imports shine for imports within the same package. They make code more portable when you rename or move packages.
my_project/package_a/module_a2.py
:
# Import from sibling module in same package
from .module_a1 import greet_a1, HelperClass
def extended_greet():
greet_a1()
print("Extended greeting from module_a2!")
helper = HelperClass("Khan")
print(f"Helper: {helper.name}")
my_project/package_b/module_b1.py
:
# Import from subpackage
from .subpackage_c.module_c1 import perform_subtask
def perform_task():
print("Task in module_b1")
perform_subtask()
my_project/package_b/subpackage_c/module_c1.py
:
# Import from parent package using ..
from ..module_b1 import perform_task
def perform_subtask():
print("Subtask in module_c1")
# Be careful - this would create circular import:
# perform_task()
Here's the trap that catches everyone: You cannot run a module with relative imports directly as a script.
This will fail:
python my_project/package_a/module_a2.py
# ImportError: attempted relative import with no known parent package
Why? When you run a Python file directly, it becomes __main__
, not part of a package. It has no "parent package" to reference with .
or ..
.
Always run your entry point (like main.py
), which uses absolute imports:
# my_project/main.py
from package_a.module_a2 import extended_greet
if __name__ == "__main__":
extended_greet()
-m
Flag for Package ModulesIf you need to run a module within a package, use the -m
flag:
# From project root
python -m my_project.package_a.module_a2
This tells Python to run the module as part of its package, so relative imports work.
main.py
, test files)__init__.py
in every package directory...
and beyond gets confusing)sys.path
unless absolutely necessaryfrom module import *
in production codeWant to install your package with pip
? Create a setup.py
:
# my_project/setup.py
from setuptools import setup, find_packages
setup(
name="my_project",
version="1.0.0",
packages=find_packages(),
install_requires=[
# Your dependencies
],
entry_points={
'console_scripts': [
'my-app=my_project.main:run',
],
},
)
Then install in development mode:
pip install -e .
Now you can import your package from anywhere:
from my_project.package_a import module_a1
You mentioned it looks like "making an object and calling it" - great intuition! Let's explore what's really happening.
When you import my_module
, Python:
my_module.py
in sys.path
types.ModuleType
)sys.modules
import sys
import my_module
# The module is an object!
print(type(my_module)) # <class 'module'>
print(sys.modules['my_module']) # Same object
When you import package_a
, Python:
package_a/__init__.py
package_a
__init__.py
in that namespacesys.modules['package_a']
So when you write:
from package_a import module_a1
module_a1.greet_a1()
You're accessing attributes on module objects, just like accessing methods on class instances!
The difference is:
# Module - only one instance
import math
import math as math2
print(math is math2) # True - same object
# Class - multiple instances
class MyClass:
pass
obj1 = MyClass()
obj2 = MyClass()
print(obj1 is obj2) # False - different objects
Let's build a practical project structure:
blog_project/
├── __init__.py
├── main.py
├── requirements.txt
├── config.py
├── models/
│ ├── __init__.py
│ ├── user.py
│ └── post.py
├── database/
│ ├── __init__.py
│ └── connection.py
├── api/
│ ├── __init__.py
│ ├── routes.py
│ └── middleware.py
└── utils/
├── __init__.py
├── validators.py
└── helpers.py
main.py
:
# Absolute imports from our packages
from config import DATABASE_URL
from database.connection import init_db
from api.routes import setup_routes
from models import User, Post # Exposed in models/__init__.py
def main():
init_db(DATABASE_URL)
app = setup_routes()
app.run()
if __name__ == "__main__":
main()
models/__init__.py
:
# Expose models at package level
from .user import User
from .post import Post
__all__ = ['User', 'Post']
models/post.py
:
# Relative import from same package
from .user import User
class Post:
def __init__(self, author: User, content: str):
self.author = author
self.content = content
api/routes.py
:
# Absolute imports from other packages
from models import User, Post
from utils.validators import validate_email
# Relative import from same package
from .middleware import auth_required
def setup_routes():
# Route setup logic
pass
Problem: Python can't find your module
Solutions:
__init__.py
exists in package directoriessys.path
Problem: Running a module with relative imports directly
Solution: Run the entry point instead, or use python -m package.module
Problem: Module A imports B, B imports A
Solution:
Python's import system is powerful once you understand the patterns:
__init__.py
to define packagesFollow these principles, and you'll have clean, maintainable Python projects that scale beautifully.
Now go structure that project like a pro! 🐍