Introduction
If you’ve explored a Python project, you’ve seen a small file named __init__.py
inside some folders. It looks trivial—but it’s a powerful signal to Python: “treat this directory as a package.”
In this guide, we’ll demystify what __init__.py
does, when it can be empty, how it impacts imports, and what changed after Python 3.3 introduced namespace packages.
Tip: I’ve also added a Q&A bot on the article page—ask questions and get instant answers while you read.
What Is __init__.py
?
In classic Python packaging, a folder becomes an importable package only if it contains an __init__.py
file.
This file:
- Marks the directory as a package
- Can run initialization code when the package is imported
- Can control what gets exported during
from package import *
Minimal example:
mypackage/
__init__.py
module1.py
module2.py
Now this works:
import mypackage.module1
from mypackage import module2
Can __init__.py
Be Empty?
Yes. An empty __init__.py
simply declares the directory as a package.
That’s often all you need.
Empty file is fine when:
- You just want the folder importable
- You don’t need startup logic or export control
- You want consistent behavior across tools and IDEs
What Can You Put Inside __init__.py
?
Plenty. But use it thoughtfully.
1) Run light initialization code
# mypackage/__init__.py
print("mypackage loaded")
This runs once when mypackage
is first imported. Keep it lightweight to avoid slow imports.
2) Re-export selected symbols (public API)
# mypackage/__init__.py
from .module1 import useful_func, Helper
__all__ = ["useful_func", "Helper"]
This lets your users write:
from mypackage import useful_func, Helper
…and keeps your internal structure tidy.
3) Package metadata (optional pattern)
# mypackage/__init__.py
__version__ = "1.0.0"
__author__ = "Your Name"
Controlling Imports with __all__
__all__
defines what gets imported on:
from mypackage import *
Example:
# mypackage/__init__.py
from .module1 import add
from .module2 import subtract
__all__ = ["add", "subtract"] # Only these export
Without __all__
, Python may import more than you intend. Explicit is better than implicit.
Modern Twist: Namespace Packages (Python 3.3+)
Python 3.3 introduced implicit namespace packages. That means a directory can be a package without an __init__.py
.
So do we still need __init__.py
?
Often, yes:
- You want explicit initialization logic
- You need
__all__
to shape the public API - Your tooling/tests expect it
- You prefer explicitness and backward compatibility
Rule of thumb:
If you care about control, clarity, and compatibility, keep __init__.py
.
Best Practices
- ✅ Keep
__init__.py
small and fast - ✅ Use it to define the public API (re-export key functions/classes)
- ✅ Prefer explicit exports via
__all__
- ✅ Add it for compatibility—even if namespace packages make it optional
- ❌ Don’t put heavy logic or large imports here (it slows every import)
- ❌ Don’t hide complex side effects in
__init__.py
Common Patterns
“Barrel” file for simplified imports
# mypackage/__init__.py
from .users import User, get_user
from .payments import Payment, charge
__all__ = ["User", "get_user", "Payment", "charge"]
Usage:
from mypackage import User, charge
Clean, discoverable, and user-friendly.
Lazy imports (advanced)
If startup time matters, consider lazy imports with small wrappers. Keep it readable; avoid premature complexity.
Quick Checklist
- Do you want the folder to be importable? ➜ Add
__init__.py
- Do you need to expose a clean public API? ➜ Re-export in
__init__.py
+ set__all__
- Do you want fast imports? ➜ Keep logic minimal here
- Targeting modern Python only? ➜ You can skip it, but explicit is better
FAQs
Q1: Is __init__.py
required in Python 3.11?
No, but it’s still recommended for clarity and control.
Q2: Can I print logs from __init__.py
?
You can, but prefer lightweight logging and avoid slowing imports.
Q3: Why doesn’t my package import without __init__.py
?
Your tooling, project layout, or environment might still expect classic packages. Add the file.
Q4: Should I put version info in __init__.py
?
Yes, many projects expose __version__
there for convenience.
Conclusion
__init__.py
may be tiny, but it shapes how your package is imported, initialized, and presented to users. Even in the namespace‑package era, it remains the cleanest way to control your package’s public API and behavior.
If you want a deeper dive or examples for your project, drop a question to the bot on this page or ping me.