Python __init__.py: The Tiny File That Turns a Folder into a Package

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.

Leave a Reply

Your email address will not be published. Required fields are marked *