Python’s special __init__.py
file marks a directory as a regular Python package and allows you to import its modules. This file runs automatically the first time you import its containing package. You can use it to initialize package-level variables, define functions or classes, and structure the package’s namespace clearly for users.
By the end of this tutorial, you’ll understand that:
- A directory without an
__init__.py
file becomes a namespace package, which behaves differently from a regular package and may cause slower imports. - You can use
__init__.py
to explicitly define a package’s public API by importing specific modules or functions into the package namespace. - The Python convention of using leading underscores helps indicate to users which objects are intended as non-public, although this convention can still be bypassed.
- Code inside
__init__.py
runs only once during the first import, even if you run the import statement multiple times.
Understanding how to effectively use __init__.py
helps you structure your Python packages in a clear, maintainable way, improving usability and namespace management.
Note: The filename __init__.py
is hard to pronounce. Most people find it easier to call it dunder-init.
You may have seen files named __init__.py
scattered throughout large Python projects and wondered exactly what they do. Or you may have used __init__.py
files yourself without a clear idea of why they’re necessary or how to exploit their features. You might also have noticed that your Python code sometimes works even if you forget to add __init__.py
to your packages.
So, what is __init__.py
for?
Get Your Code: Click here to download the free sample code that shows you how to use Python’s __init__.py
.
Take the Quiz: Test your knowledge with our interactive “What Is Python's __init__.py For?” quiz. You’ll receive a score upon completion to help you track your learning progress:
Interactive Quiz
What Is Python's __init__.py For?Test your understanding of Python's __init__.py files to master how they shape your packages, enhance project structure, and keep your code clean.
In Short: __init__.py
Declares a Folder as a Regular Python Package
The special file __init__.py
serves as a marker to indicate that its containing directory is a regular package. When people talk about a package in Python, they generally mean a regular package. The __init__.py
file is a Python source file, which means that it’s also a module.
If you want to review the terms module and package and how they’re used in Python, then expand the collapsible section below:
In Python, the terms module and package both describe units containing code:
Module | Package | |
---|---|---|
Definition | A single Python file containing code | A directory containing one or more Python modules |
Naming | The filename without the .py extension |
The directory name |
Contents | Functions, classes, and variables | Modules and optionally subpackages |
Purpose | Organizing small projects, simple code reuse | Organizing large projects, code reuse |
Importing | Import directly (import module ) |
Import package or its modules (import package.module ) |
As you can see, modules and packages both help organize Python code, with modules representing single files and packages representing directories that can group multiple modules.
The existence of __init__.py
makes the Python loader identify its containing directory as a regular package. This means you can import the whole directory, specific modules within it, or even individual functions, variables, and classes from those modules using the package name.
You can also import those code objects into other modules, which allows your code to be reused in many flexible ways. You’ll see several examples of this shortly.
As mentioned, __init__.py
may be an empty file, in which case it serves only to identify a package.
What Happens When I Add Code to __init__.py
?
If __init__.py
does contain code, then that code will be executed when the package is first imported.
The code in __init__.py
can perform a variety of useful tasks. For example, it can import other modules or packages and define its own functions or data. When a package is imported, all of its own code, along with everything it has itself imported, becomes accessible to the importing module.
You’ll start with a simple example based on a single package. Here’s the setup:
tools_project/
│
└── tools/
└── __init__.py
So, your project is named tools_project
. It has a package named tools
. The tools/__init__.py
file could have been completely empty, but here it contains a few lines of code:
tools_project/tools/__init__.py
__version__ = "1.0.0"
magic_number = 42
With those files in place, start a REPL session in the tools_project
directory, and watch what happens when you import and use the tools
package:
1>>> import tools
2>>> tools.__version__
3'1.0.0'
4>>> tools.magic_number
542
When you executed import tools
in line 1, the __init__.py
file in the tools
directory was implicitly executed. That made the names in tools/__init__.py
accessible via the tools
prefix. Typically, you might use __init__.py
to declare variables that are relevant for the package you’re importing, like the package version or a package-wide constant.
Note: The code in __init__.py
is executed only once, even if you import the package multiple times. You’ll see this in more detail later on.
In the rest of this tutorial, you’ll learn more about when, why, and how you might want to use __init__.py
.
What Happens if I Don’t Add an __init__.py
File?
So far, you’ve been learning about regular packages, but Python also supports packages of another type, known as namespace packages. A namespace package doesn’t have an __init__.py
file. You can still import it, provided Python can find it through sys.path
. If you don’t add an __init__.py
file to a directory, then it will be treated as a namespace package.
Both regular packages and namespace packages define namespaces. The difference lies in the fact that a namespace package isn’t limited to a single directory—it’s the namespace prefix, not the physical directory, that identifies the package contents.
Note:
Namespace packages have been around for many years, but they used to require some extra setup. Since the adoption of PEP 420 in Python 3.3, the language has had implicit namespace packages, identified only by the absence of __init__.py
. This makes them easier to create intentionally, but also easier to create by accident!
Recall that a namespace is a convention for distinguishing different code objects that may happen to have the same base name, whether that’s the name of a variable, a function, a class, or anything else. By using different prefixes, you can avoid confusion.
For example, in a large body of code, you may very well have two distinct modules named utils
. Suppose that one of these contains finance utilities, while the other contains payroll utilities. By placing them in different namespaces—called finance and payroll—you allow Python to distinguish between them, even though they share the same base name. This is because Python treats finance.utils
and payroll.utils
as entirely separate code objects.
You can easily add a namespace package to your demonstration project. Just add a new, empty directory below tools_project
:
tools_project/
│
├── tools/
│ └── __init__.py
│
└── tools_ns/
Recall that both modules and packages must be valid Python identifiers. Hence, your new directory name contains an underscore (_
) rather than, say, a hyphen (-
).
Now, in the tools_project
directory, use the REPL to verify that you can import this package too. Every Python package has a .__path__
attribute. Notice that tools_ns.__path__
looks a little different from tools.__path__
:
>>> import tools
>>> tools.__path__
['/tools_project/tools']
>>> import tools_ns
>>> tools_ns.__path__
_NamespacePath(['/tools_project/tools_ns'])
Python finds tools_ns
in the current directory, notes that it has no __init__.py
within it, and imports it as a namespace package.
Although tools_ns
was successfully imported, its .__path__
attribute indicates that it’s a namespace package, not a regular package.
Note: The Python runtime maintains a list of directories called sys.path
that it uses initially to load its own standard libraries and later to locate any imports declared in your code. The list includes paths to any libraries you’ve installed, as well as the current directory of the running script.
You can add more directories to sys.path
either by including them in the PYTHONPATH
environment variable, or by manipulating sys.path
at runtime. That’s usually unnecessary, though, as your locally-developed code will be found in the script’s own directory and its subdirectories. That’s how Python finds tools
and tools_ns
in this example.
Namespace packages are useful for combining multiple codebases into a unified naming structure. For example, a corporation with a number of development sites might want to combine their various contributions under a single namespace.
For instance, a company called MegaCorp Inc. might use the namespace com.megacorp
. Rather than force users to download all the code within their namespace in a single bundle, they can publish smaller, separate libraries that all share the same prefix, such as com.megacorp.finance
and com.megacorp.sales
.
If MegaCorp publishes these as separate packages, they can be individually maintained, distributed, and used. This approach can be applied to any large software ecosystem or product where logical separation and modularity are key.
Namespace packages certainly have their uses, but they also come with a few caveats. One is that when importing a namespace package, the interpreter must search the whole sys.path
to ensure that it has found all the files contributing to the namespace. This results in slower imports. Also, because the full namespace can only be assembled at runtime, any name collisions aren’t detected until then.
Unless you’re sure that you want to build a namespace package, you should opt for regular packages by including an __init__.py
file within each package in your project. This will result in more efficient and predictable imports.
The rest of this tutorial will be about regular packages.
What Kind of Code Should I Put in __init__.py
?
There’s no requirement to include any executable code inside __init__.py
. As you’ve already learned, it’s common to create an empty __init__.py
file in a directory, where it serves as a marker to indicate that the directory should be treated as a regular package.
However, __init__.py
can also contain functions, classes, data, and absolute or relative import statements.
This can be useful for importing names from submodules to structure a sensible namespace for users of the top-level package. Any names you import into the package become part of its top-level namespace and can then be more conveniently referenced by client code.
__init__.py
is a good place to define utility functions, constants, and variables that apply to the whole package.
In summary, you can use the __init__.py
file to:
- Import other modules that are needed by code within
__init__.py
itself - Define variables or functions at the package level
- Set up configuration or settings for the package
- Include documentation or define metadata for the package
- Provide a simplified, coherent namespace for package users
Now that you know what kind of code you may want to include in __init__.py
, you may be wondering when Python executes the code in this file.
When Does __init__.py
Get Executed?
An __init__.py
file is executed the first time its package is imported. This will happen:
- If you import the package explicitly
- If you import a module within the package
- If you import a subpackage of the package, or a module within that subpackage
The __init__.py
file starts executing as soon as the interpreter encounters the corresponding import
statement. Any nested imports within the file will also be executed as they’re encountered.
How Is __init__.py
Used in a Practical Example?
Here’s a concrete example of how to use __init__.py
. Suppose you’re starting a new project called media_project
, with its own root directory. Below that, a directory named mediatools
contains its own __init__.py
, defining the mediatools
package and namespace. Within that package, you might include subpackages named audio
and graphics
. Here’s the layout:
media_project/
│
└── mediatools/
│
├── audio/
│ ├── __init__.py
│ └── utils.py
│
├── graphics/
│ ├── __init__.py
│ └── utils.py
│
└── __init__.py
Within media_project
, you’ve created the package mediatools
, which contains two subpackages: mediatools/audio
and mediatools/graphics
. The module mediatools/audio/utils.py
contains audio utility functions, constant declarations, and the like, while mediatools/graphics/utils.py
plays a similar role for the graphics part of your project.
Notice that each package has its own __init__.py
, so these are regular packages.
For the purposes of this example, you’ll populate the mediatools/audio/utils.py
module as follows:
mediatools/audio/utils.py
1print(f"Importing {__name__}")
2
3def wobbulate():
4 print("Wibble wobble")
5
6def enhance():
7 print("Enhancing audio")
8
9_secret_password = "Alakazam!"
10__top_secret_password = "!mazakalA"
You’ll be adding a line like line 1 to each Python file from now on. That way, you’ll see exactly when that package or module is imported.
The two variables _secret_password
and __top_secret_password
in lines 9 and 10 are called non-public variables. They’re not meant to be used outside their own module. You’ll soon explore what this means in practice.
You’ll also add some code to mediatools/graphics/utils.py
:
mediatools/graphics/utils.py
print(f"Importing {__name__}")
def solarize():
print("Solarizing")
def enhance():
print("Enhancing graphics")
indiana_pi = 3.2
So the audio
and graphics
packages each contain a module named utils.py
, and each of those contains a debugging call to print()
at the top that’ll display the module’s name, some function definitions, and variable declarations.
For example, the audio.utils
module contains the function wobbulate()
. If you had a main program in the media_project
directory, it could arrange to call that function in various ways, such as by importing the module with its full namespace prefix:
import mediatools.audio.utils
mediatools.audio.utils.wobbulate()
Or by importing just the module name:
from mediatools.audio import utils
utils.wobbulate()
Or by importing the function name with no prefix at all:
from mediatools.audio.utils import wobbulate
wobbulate()
Next, add your demonstration call to print()
to mediatools/__init__.py
, so you’ll know when it’s executed:
mediatools/__init__.py
print(f"Importing {__name__}")
Add the same line to graphics/__init__.py
:
mediatools/graphics/__init__.py
print(f"Importing {__name__}")
And also to audio/__init__.py
:
mediatools/audio/__init__.py
print(f"Importing {__name__}")
With these debugging calls to print()
set up, you’ll be able to observe when the code in each __init__.py
is executed. This happens when its package is imported. You can see this in action by starting a new REPL session:
>>> from mediatools.audio import utils as audio_utils
Importing mediatools
Importing mediatools.audio
Importing mediatools.audio.utils
>>> from mediatools.graphics import utils as graphics_utils
Importing mediatools.graphics
Importing mediatools.graphics.utils
>>> audio_utils.wobbulate()
Wibble wobble
>>> graphics_utils.solarize()
Solarizing
>>> graphics_utils.indiana_pi
3.2
In this code, you’ve chosen the prefix audio_utils
to indicate that a function comes from the utils
module within the audio
package. Similarly, the prefix graphics_utils
identifies a function from the utils
module within the graphics
package.
Even though your import
statements explicitly mentioned only the two utils
modules, your calls to print()
show that the code in mediatools.__init__.py
, mediatools.audio.__init__.py
, and mediatools.graphics.__init__.py
was implicitly executed. So, the first three lines printed to your console after importing the utils
module from mediatools.audio
come from the following files:
mediatools/__init__.py
printsImporting mediatools
mediatools/audio/__init__.py
printsImporting mediatools.audio
mediatools/audio/utils.py
printsImporting mediatools.audio.utils
Notice that the code in mediatools.__init__.py
was executed only once, even though mediatools
is a parent package of both the imported modules. You’ll see why this happens in a moment.
You’ve confirmed that the code in the __init__.py
file runs automatically when its package, or anything within it, is imported. While this code can do nearly anything, its usual purpose is to initialize the package and define any attributes, functions, or data that are global to the package. It may also perform imports, either for its own internal use or to expose the imported names to client code.
The Python interpreter maintains a cache of imported modules, so you can’t import the same module twice using a normal import
statement. Python silently skips the second import
statement, and the contents of __init__.py
won’t be re-executed. If you import two subpackages from the same parent package, then the parent and each subpackage will be imported exactly once.
See what happens when you start a new REPL and attempt to load audio.utils
twice:
>>> from mediatools.audio import utils
Importing mediatools
Importing mediatools.audio
Importing mediatools.audio.utils
>>> from mediatools.audio import utils as more_utils
>>> utils.enhance()
Enhancing audio
>>> more_utils.enhance()
Enhancing audio
The second import
statement didn’t execute any imports—it only aliased the previous import utils
to the name more_utils
.
Note: You can force re-execution of an __init__.py
file with a dynamic import using Python’s importlib
library:
>>> import mediatools
Importing mediatools
>>> import importlib
>>> mediatools = importlib.reload(mediatools)
Importing mediatools
However, keep in mind that you should use dynamic imports and reloading selectively—only when Python’s standard, static imports don’t provide enough flexibility or when you explicitly need runtime control over modules.
Before continuing, you can remove the debugging calls to print()
from all your files to avoid cluttering your output in the following sections.
Can I Use __init__.py
to Control What’s Exported From a Package?
The short answer is this: not really. Python does have mechanisms and conventions to control exports, but they’re easily circumvented. One such convention concerns names that start with one or more leading underscores. The convention dictates that external code shouldn’t reference these names. By using these “underscore names,” the package developer can help the package user to understand what should and shouldn’t be included in the public API.
In practice, it’s the package developer’s responsibility to use this convention to indicate the things that aren’t intended for export, but it’s up to the package user to respect the convention. It can easily be ignored, as you can see in the following code snippet:
>>> import mediatools.audio.utils
>>> mediatools.audio.utils._secret_password
'Alakazam!'
>>> mediatools.audio.utils.__top_secret_password
'!mazakalA'
Python is happy to import and print the values of these supposedly non-public variables.
The import *
syntax does respect the underscore convention. You can use this syntax to import the contents of the module mediatools/audio/utils.py
in a new REPL session:
>>> from mediatools.audio.utils import *
>>> _secret_password
Traceback (most recent call last):
...
NameError: name '_secret_password' is not defined
>>> __top_secret_password
Traceback (most recent call last):
...
NameError: name '__top_secret_password' is not defined
With this syntax, most of the names inside mediatools.audio.utils
become available to your program without the prefix, but the underscore variables aren’t imported.
Note: The import *
syntax is often convenient in interactive work, but it’s considered bad practice in larger projects because it erases the module namespace information. All the objects defined in mediatools.audio.utils
—except those that start with an underscore—are imported and lose their prefix in the process. They become part of the global namespace, increasing the risk of name collisions.
To see this issue in practice, start a new REPL session in the media_project
directory and use import *
for your submodules:
>>> from mediatools.audio.utils import *
>>> from mediatools.graphics.utils import *
>>> wobbulate()
Wibble wobble
>>> indiana_pi
3.2
>>> solarize()
Solarizing
>>> enhance()
Enhancing graphics
The interpreter can no longer distinguish between the two versions of enhance()
. In fact, the second import *
imported the name enhance
, without a namespace prefix, from graphics.utils
. This clobbered the enhance
imported from audio.utils
, resulting in unexpected behavior.
Note:
Python provides a special list named __all__
that, when defined in a module, controls which names are imported by a from module import *
statement. Only the names listed in __all__
will be imported.
Most programmers will follow best practices and avoid import *
in production code. However, __all__
is also respected by documentation tools when defining the code’s public API. IDE autocompletion tools also check __all__
to ensure that only public code completions are suggested.
You can demonstrate the use of __all__
by adding a line to mediatools/graphics/utils.py
:
mediatools/graphics/utils.py
# ...
__all__ = ["solarize", "enhance"]
With this addition, import *
will import only the two named functions, and the variable indiana_pi
is no longer visible:
>>> from mediatools.graphics.utils import *
>>> indiana_pi
Traceback (most recent call last):
...
NameError: name 'indiana_pi' is not defined
Although you can’t prohibit your package’s users from importing symbols in a way you didn’t intend, you can structure your namespace in a way that helps them understand how the code is meant to work.
For example, instead of expecting a developer to dig into the details of the two utils
packages to decide what to import and how to import it, you can add a couple of lines to mediatools/__init__.py
to make things a little more self-explanatory:
mediatools/__init__.py
from .audio.utils import enhance as audio_enhance
from .audio.utils import wobbulate
from .graphics.utils import enhance as graphics_enhance
from .graphics.utils import solarize
Here you’ve used some relative imports to simplify the developer’s view of the mediatools
package. This conforms to the software engineering principle of information hiding: the package user doesn’t need to see the gory details of mediatools
and its subpackages. They simply need an interface definition, as shown above, to help them to use the package at a high level.
Now the user code can take advantage of the new interface:
>>> from mediatools import (
... wobbulate,
... solarize,
... audio_enhance,
... graphics_enhance
... )
>>> wobbulate()
Wibble wobble
>>> solarize()
Solarizing
>>> audio_enhance()
Enhancing audio
>>> graphics_enhance()
Enhancing graphics
Using this interface, client code is better decoupled from the internals of the mediatools
packages. This makes the client code easier to write and easier to maintain. Notice that your single import
statement caused a chain reaction of imports: first the parent package, then each subpackage with its module.
Sometimes, you want to import packages into __init__.py
for its internal use, but to avoid polluting the namespace, you don’t want to expose those names to the users of your package.
Here’s a schematic __init__.py
file that does this for an imaginary application:
from datetime import date as _date
from math import gcd as _gcd
from some.thirdparty.dateutils import lunar_month as _lunar_month
def fancy_date_calculator() -> str:
today = _date.today
# Some fancy calculations using _gcd and _lunar_month
...
return "Calculated result"
When user code imports this package, it will see only one public function: fancy_date_calculator()
. All the other symbols will be prefixed with underscores, making it clear that they’re not intended for public use.
Is a Top-Level __init__.py
Different From One in a Subpackage?
The __init__.py
in the folder at the top level of an import tree often contains some extra metadata about the library as a whole. This can include its version number, the URL of its project site, and contact information for the developers. Otherwise, it behaves in the same way as any other __init__.py
.
For example, say you want to add a package version number. A good spot for that would be inside of mediatools/__init__.py
:
mediatools/__init__.py
__version__ = "0.1.0"
# ...
You’d likely keep the other __init__.py
files empty, although nothing prevents you from recording version information for your subpackages there as well.
Conclusion
In this tutorial, you’ve learned about the special role of Python’s __init__.py
file in defining regular packages and distinguishing them from namespace packages.
You’ve explored how Python executes code within __init__.py
upon package import, how to use it to structure namespaces and APIs, and how to handle imports effectively. Additionally, you’ve learned about Python’s underscore naming conventions for non-public symbols and how the __all__
variable can help explicitly define a module’s public interface.
Effectively using __init__.py
can help you create well-organized, maintainable, and intuitive packages. By structuring your packages properly, you improve readability, encourage reusability, and simplify package-level initialization.
In this tutorial, you’ve learned how to:
- Mark directories as regular packages using
__init__.py
- Define a clear, explicit public API using imports in
__init__.py
- Use underscore prefixes to indicate non-public symbols
- Control what’s imported with the
__all__
variable - Understand when and how Python executes code in
__init__.py
Next time you create an __init__.py
file, you’ll understand exactly how it fits into your project and be ready to add code that structures your namespaces, initializes data, or stores metadata.
Get Your Code: Click here to download the free sample code that shows you how to use Python’s __init__.py
.
Frequently Asked Questions
Now that you have some experience with __init__.py
in Python, you can use the questions and answers below to check your understanding and recap what you’ve learned.
These FAQs are related to the most important concepts you’ve covered in this tutorial. Click the Show/Hide toggle beside each question to reveal the answer.
An empty __init__.py
file indicates to Python that the directory is a regular package, allowing you to import modules from it.
Without __init__.py
, Python recognizes the directory as a namespace package instead of a regular package. Namespace packages can span multiple directories, but their imports are usually slower.
You can’t strictly enforce what’s imported, but you can guide users by explicitly defining a public API within your __init__.py
file. Python conventions like leading underscores and the __all__
variable help users understand your intended use.
Python executes the code in __init__.py
the first time you import the package or any of its modules. Python runs this code only once, caching the package for subsequent imports.
Take the Quiz: Test your knowledge with our interactive “What Is Python's __init__.py For?” quiz. You’ll receive a score upon completion to help you track your learning progress:
Interactive Quiz
What Is Python's __init__.py For?Test your understanding of Python's __init__.py files to master how they shape your packages, enhance project structure, and keep your code clean.