skip to content

Modules and Regular Packages

The difference between a module (one .py file) and a package (a directory Python imports as a namespace), and what __init__.py is for. Since 3.3 it is optional, so knowing when to keep one is the probe.

part ofPythonoverview, primer and where to startread it →
on this pageshow

questions

4

What distinguishes a Python package from a plain module, and what does __init__.py do?

level: juniorimportance: must knowfreq 70%

answer

  1. One unit of code, or a container
  2. Both are the same runtime object type
  3. One attribute decides which is which
  4. __path__ marks a package
  5. __init__.py is the package body

basics

~20 s

A module is one importable unit of code, usually a single .py file. A package is a module that can also contain submodules: a directory whose init.py file is executed as the package's own body when it is first imported.

solid answer

~40 s

At runtime both are the same thing — a `types.ModuleType` object cached in `sys.modules` under its dotted name. The structural difference is one attribute: a package has `__path__`, the list of directories searched when resolving its submodules; a plain module does not. For a *regular* package the directory's `__init__.py` is the package body: importing `pkg` executes that file once, with the package's namespace as its globals, so every name it binds becomes an attribute of the `pkg` module object — that is how re-exports work. Its `__file__` therefore points at `pkg/__init__.py`, not at the directory. Since Python 3.3 (PEP 420) the file is optional, because a directory without one still imports as an implicit namespace package, so adding `__init__.py` is now a deliberate choice: you want package-level code to run.

code

python · 8 lines
python
import types
import xml
import xml.etree.ElementTree as ET

print(type(xml) is types.ModuleType)      # True: a package is a module object
print(hasattr(xml, "__path__"))           # True: xml is a package
print(hasattr(ET, "__path__"))            # False: a plain module
print(xml.__file__.endswith("__init__.py"))  # True: the body, not the directory

go deeper

for a junior

Be ready to state the difference in one breath: a module is one file of code, a package is a directory Python can import that holds submodules, and init.py is the file that runs when the package is imported.

for a middle

Explain the mechanics: both are types.ModuleType objects in sys.modules, path is what makes one a package, and init.py executes once as the package body so the names it binds become package attributes.

for a senior

Show judgement about what belongs in that body. An interviewer expects you to know it runs for every consumer of any part of the package, and to treat import-time side effects as a liability.

for a principal

Own the convention across a codebase: whether packages carry init.py at all, what may run at import time, and how that policy keeps startup cost and import-order coupling out of the team's shared modules.

## Two words, one runtime object Everything Python imports ends up as the same kind of object: an instance of `types.ModuleType`, whose `__dict__` is the module's global namespace, registered in `sys.modules` under its fully qualified dotted name. A *module* and a *package* are both that object. The difference is a single attribute: a package has `__path__`, a list of directories the import system searches when it resolves a submodule of it; a plain module does not. That is the whole definition. `hasattr(mod, "__path__")` is the runtime test, and `mod.__spec__.submodule_search_locations is not None` is the same test spelled through the module's spec. ## What a regular package is on disk A *regular* package is a directory containing `__init__.py`. When you `import pkg`, the import system finds the directory, creates a module object named `pkg`, sets `__path__` to that directory, and then executes `__init__.py` **as the module's body**, using the module's `__dict__` as its globals. So `__init__.py` is not configuration, not metadata, and not boilerplate the interpreter reads and discards — it is the package's own source file. Every name it binds (a constant, a function, a class, or a submodule it imports) becomes an attribute of the `pkg` module object. That single fact is the entire mechanism behind re-exports: `from .ledger import post` written inside `pkg/__init__.py` is just an assignment into the package's namespace, which is why consumers can then write `from pkg import post`. ## __file__ and __path__ on a package Because `__init__.py` *is* the body, `pkg.__file__` is the path to `pkg/__init__.py`, not to the directory; the directory itself appears in `pkg.__path__`. Code that wants "the directory my package lives in" and reaches for `__file__` gets the file, and must take its parent — a small, extremely common bug. ## Execution happens once per process The first import runs the body and inserts the module into `sys.modules`; every later `import pkg` anywhere in the process is a dictionary lookup that merely rebinds a name. Importing a submodule imports its parents first, so `pkg/__init__.py` always runs before `pkg/ledger.py`. The parent body has therefore at least *started* running when a child body executes — the qualifier matters, because a parent that imports its own children is how partially initialized modules and circular-import errors arise. ## Since 3.3 the file is optional PEP 420, shipped in Python 3.3 and still the behaviour on 3.14, made a directory with no `__init__.py` importable as an *implicit namespace package*. Such a package still gets `__path__`, but that path can be assembled from several parent directories, and it has no `__file__` and no body to execute. So on modern Python `__init__.py` is a choice with consequences rather than a requirement. Keep one when you want code to run at package import time — defining package-level names, exposing a small surface, registering something — and when you want that directory to be one concrete, self-contained package rather than a fragment that can merge with a same-named directory elsewhere on `sys.path`. Leave it out only deliberately. ## Modules that are not .py files A module need not be Python source at all. A compiled extension module imports to the same module object, with `__file__` pointing at the shared library instead of at a `.py` file. Built-in modules compiled into the interpreter — `sys` is the standard example — have no `__file__` whatsoever, which is why `__file__` is one of the few module attributes code must not assume exists. ## The vocabulary trap "Package" means two unrelated things in Python, and interviewers do probe the gap: the *importable* package described here, and an installed *distribution*, the thing you install from an index and query with `importlib.metadata`. The two names frequently differ for the same project, and one distribution may install several importable packages, or none. When someone reports "the package is installed but the import fails", the first question is which of the two they mean. ## What to say in the room Module equals one importable unit of code, usually one file. Package equals a module that can contain submodules, marked by `__path__`. A regular package is a directory whose `__init__.py` is executed as the package body on first import, once per process, before any of its submodules. It has been optional since 3.3, so when it is there, it is there because someone wanted code to run.

  • How do you test at runtime whether an imported name is a package or a plain module?
    Check for `__path__`: `hasattr(mod, "__path__")` is true only for packages, since that list is what the import system searches for submodules. The spec spelling is `mod.__spec__.submodule_search_locations is not None`. Do not test for `__init__.py` on disk — a namespace package is a package with no such file.
  • If __init__.py has been optional since 3.3, why would you still write one?
    Because you want something to happen at package import: binding package-level names, exposing a handful of names so callers need not know the submodule layout, or running registration code. It also pins the directory as one concrete package rather than a fragment that could merge with a same-named directory further along `sys.path`. An empty `__init__.py` still carries that second meaning.
  • Is a compiled extension module a module in the same sense as a .py file?
    Yes. It imports to the same `types.ModuleType` object, lands in `sys.modules` under its dotted name, and has `__file__` pointing at the shared library rather than at Python source. Nothing about the import system's model changes; only the loader that produced the module differs.

A module is a single sheet of paper; a package is a labelled folder, and init.py is the cover page inside that folder, read aloud the moment the folder is opened.

saying these in an interview costs you the question

  • Says a package is just a folder, with no module object
  • Thinks __init__.py is boilerplate the interpreter merely checks for
  • Claims __init__.py is still mandatory on modern Python
  • Believes __init__.py re-runs for every submodule import
  • Says a package's __file__ is the directory path
  • Confuses an importable package with an installed distribution

context

open as a page

Why does `import xml` leave `xml.etree` an AttributeError while `import xml.etree` works?

level: middleimportance: should knowfreq 45%

basics

~20 s

Importing a package does not import its submodules. Plain import xml runs only xml/init.py; the name etree becomes an attribute of the xml module object only once xml.etree is itself imported, by you or by someone else.

open as a page

A package's __init__.py imports all its submodules eagerly — what does that cost you?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Everything init.py imports is paid for by every process that touches any part of the package, before any of its own code runs. That means slower start-up, more memory, import-time side effects, and hidden coupling between submodules.

open as a page

What do __name__, __file__ and __path__ hold on an imported Python package?

level: middleimportance: nice to knowfreq 20%

basics

~20 s

name is the module's full dotted import name; file is the file it was loaded from, which for a regular package is its init.py rather than the directory; path is the submodule search list only packages have.

open as a page