Learn by reading through in order

__init__.py and Relative Imports — Bundling Files into a Package

Learn the role of __init__.py for building Python packages and when to reach for absolute vs. relative imports.

The previous article covered single-file modules and how import works. Once an application grows, you'll want to bundle related modules under one folder as a package. This article walks through how to import packages.

A Package Is a Folder Containing __init__.py

A package is a folder that contains a special file called __init__.py. When Python finds it, the whole folder becomes one package and other code can pull it in with import folder_name. __init__.py is the first thing that runs when the package is imported, and it's a common place to declare which functions and classes the package exposes.

Anatomy of a Package
my_package/ (a package)
  • __init__.py — the file that runs first when the package is imported
  • calculation.py — a module (add / multiply)
  • string_utils.py — a module (format_name, etc.)
The my_package/ folder contains __init__.py, so Python treats the folder as a package. The internal modules like calculation.py get exposed externally through __init__.py.

__init__.py can be empty. Even an empty file is enough — Python only needs the file's presence to recognize the folder as a package.

Importing Without Going Through __init__.py

Even with an empty __init__.py, you can still import a module directly from the package. The form is from package_name.module_name import function_name — connect the file's location with dots.

# my_package/__init__.py  <- empty is fine

# my_package/calculation.py
def add(a, b):
    return a + b


# In main.py
from my_package.calculation import add   # spell out the file name `calculation`
print(add(1, 2))   # 3

The downside: callers need to know which file holds what. If you reorganize the package later (say, split calculation.py into two), every caller has to change too. The next section's pattern of re-exporting through __init__.py fixes that — it lets callers refer to package-name-direct, short names.

Collecting the Public API in __init__.py

Write from .module_name import function_name inside __init__.py and the function appears as if it lives directly under the package. For example, from .calculation import add, multiply inside __init__.py lets callers write from my_package import add, multiply — short and clean.

Re-exporting Through __init__.py for Shorter Imports
my_package/main.py__init__.pyfrom .calculationimport add, multiplycalculation.pydef add()def multiply()from my_packageimport addadd(1, 2)callablevialifts up
When __init__.py pulls add up from calculation.py, callers can write from my_package import add without thinking about which file (calculation) contains it.

The my_package/ folder is attached on the left (open calculation.py and __init__.py from 📂 Files to inspect).

① Use from my_package import add, multiply to load both functions.

② Print the results of add(10, 20) and multiply(3, 4).

Python Editor

Run code to see output

Why Collect the Public API in __init__.py

If callers only ever touch what __init__.py exposes, you can reshuffle internal files later without touching caller code. That's classic encapsulation at the package level — the basic pattern of package design.

from vs. import — Two Ways to Write It

There are two forms of import: from package import name and import package. Both pull in the target, but how you call afterwards is different.

from vs. import — What Lands in Scope
from my_pkg.calcimport addin scope:addcall:add(1, 2)importmy_pkg.calcin scope:my_pkgcall:my_pkg.calc.add(1, 2)
from package import name brings the name itself into scope so you can use it directly. import package only brings the package name into scope; calls have to use the full dotted path.
# Form 1: from ... import ...
from my_package.calculation import add
print(add(1, 2))   # add is directly usable


# Form 2: import ...
import my_package.calculation
print(my_package.calculation.add(1, 2))   # call with the full dotted path


# Form 2 + as alias (shortens long names)
import my_package.calculation as calc
print(calc.add(1, 2))
FormName in scopeHow to call
from my_package.calculation import addaddadd(1, 2)
import my_package.calculationmy_packagemy_package.calculation.add(1, 2)
import my_package.calculation as calccalccalc.add(1, 2)

The most common form is from package import name — the function name is short and main-side code reads cleanly. But when two different packages export the same function name and you need both, the import package form keeps the module name visible so it's obvious which one you're calling.

from vs. import — What Lands in main's Scope
from my_pkg.calc import add
  • add — the name itself lands in main's scope
  • Call: add(1, 2) works directly
  • Short to write, but it's not obvious which package add came from
import my_pkg.calc
  • my_pkg — only the package name lands in main's scope
  • Call: my_pkg.calc.add(1, 2) with the full dotted path
  • Longer to write, but my_pkg.calc.add makes the origin obvious
from package import name brings the name itself into main's scope (= short calls). import package brings only the package name, so calls use the full dotted path (= the origin is explicit).

Try both forms with the attached mathlib/ package. calculator.py defines triple(n) (verify in 📂 Files).

① Use the from ... import ... form to bring in triple, then print(triple(7)).

② Then use the import ... as ... form to also bring in mathlib.calculator under the alias calc, and print(calc.triple(7)).

Python Editor

Run code to see output

Absolute vs. Relative Imports

What if modules within a package need to refer to each other? Say utility/validator.py wants to import from utility/helper.py — there are two ways to write it: absolute import and relative import. To choose between them, first you need to be clear on what the project root means.

What the Project Root Is
project/ ← the root (where main.py sits)
  • main.py ← the entry point you run first
  • config.py ← app-wide settings
my_app/
  • __init__.py ← runs when import my_app happens
utility/
  • __init__.py
  • validator.py
  • helper.py
The project root is the folder where main.py (the entry point) sits. The my_app in the absolute import from my_app.utility import validator refers to the same-named folder directly under that root.

The project root is the folder that holds the entry point you run with python main.py. When you write the absolute import from my_app.utility import validator, Python looks for my_app directly under that root, then drills into utility/validator.py. The dotted path mirrors the folder structure — that's all an absolute import is.

TypeFormWhat it means
Absolute importfrom my_app.utility import helperFull path from the project root
Relative importfrom . import helperRelative to the file you're in
Relative import (parent)from .. import configTargets a file one folder up
Two Ways to Read helper.py from validator.py
Absolutefrom my_app.utilityimport helperFull pathfrom the rootRelativefrom . importhelperBased onthe current folder
When you read a neighbor module in the same package, both absolute and relative work. The . in from . import means "the same folder I'm in".

The usual split: the entry point (main.py) uses absolute imports to reach into packages, and modules inside a package use relative imports to refer to each other. With relative imports, renaming a package folder later doesn't force changes to the internal code.

# my_app/utility/validator.py
# Relative import for helper.py in the same package
from .helper import log_message


def validate_user(user):
    if user.name and user.email:
        log_message("OK")
        return True
    log_message("problem detected")
    return False


# my_app/utility/helper.py
def log_message(message):
    print("[LOG]", message)


# To reach config.py in another package,
# go up one with ..:
# from ..config import get_config
Folder Layout for the Code Above
my_app/
  • config.py ← target of from ..config import ...
utility/
  • validator.py ← writes from .helper import log_message
  • helper.py ← provides log_message(...)
validator.py and helper.py sit side by side in the same utility/ folder. That's why from .helper import log_message in validator.py uses . — "this folder (= utility/)". config.py is one level up in my_app/, so from validator's view you'd reach it as ..config (.. = one level up).

Entry Points Can't Use Relative Imports

A file you run directly with python main.py can't use relative imports — you'll hit ImportError. Relative imports only work when the file is loaded as a member of some package. From the entry point, always use absolute imports (e.g., from my_app.utility import validate_user).

Try absolute and relative imports with the attached shop/ package. Open 📂 Files and you'll see shop/__init__.py is empty — neither Cart nor to_yen is exposed at the package root yet.

① First, run main.py as-is — you'll get an ImportError because nothing is exported from shop.

② Next, open shop/__init__.py from 📂 and write from .cart import Cart and from .formatter import to_yen as relative imports (. = this package), then save.

③ Add the Cart usage to main.py — build a Cart, add apple for $100 and orange for $200, then format the total with to_yen() and print it. Main side uses absolute import, package internals use relative imports — that's the split.

Python Editor

Run code to see output

A Real-Project-Style Layout

With these rules, you can put together folder structures that match real apps. Here's a layout split into three packages — settings, database, and utility:

A Layout Close to a Real Project
project/ (project root)
  • main.py — entry point (the file you run with python)
  • config.py — app-wide settings
my_app/
  • __init__.py — public API of the my_app package
database/
  • __init__.py
  • connection.py / models.py
utility/
  • __init__.py
  • validator.py / helper.py
The entry point main.py pulls in the whole my_app/ package; modules inside use relative imports to reach each other. Each subfolder has its own __init__.py collecting that subpackage's public API.

From main.py you call out with absolute imports like from my_app.utility import validate_user. Inside utility/, validator.py reaches helper.py with from .helper import log_message — a relative import. That's the canonical division of labor.

The Boundary Between Absolute and Relative
my_app/utility/main.pyvalidator.pyhelper.pyabsoluterelative
main.py reaches into the utility package with absolute (path from the root); inside utility, validator → helper goes relative (path from where you are). Knowing the boundary keeps later folder renames from rippling out.

Implement validator.py inside the my_app/utility/ package yourself and call it from main.py (open validator.py from 📂 Files, edit, then save with Cmd+S or 💾). helper.py is already done.

① Implement validate_user(email) in validator.py:

- Start with from .helper import log_message — a relative import of helper.py in the same folder

- If email contains both @ and ., call log_message("OK: " + email) and return True

- Otherwise call log_message("problem detected: " + email) and return False

② In main.py, import it with absolute import (from my_app.utility import validate_user) and print(validate_user("alice@example.com")).

Python Editor

Run code to see output

From here on it's advanced — feel free to circle back if you get stuck

The next exercise is the capstone — building two packages in parallel. You'll have the smoothest time if the __init__.py re-export pattern feels natural and the absolute / relative import distinction is solid for you. If you get stuck, jump back to the previous validator.py exercise or the "A Real-Project-Style Layout" diagram above to reset before tackling this.

A Multi-Folder Challenge

Time to try building two packages in parallel with everything you've learned. You'll manage a product catalog/ package and a billing/ package in separate folders, then orchestrate them from main.py to complete one order. Splitting responsibilities across folders means changes to product data only touch catalog/, and changes to invoice formatting only touch billing/ — the blast radius stays inside one folder.

Project Layout (catalog and billing as Two Packages)
project/ ← project root
  • main.py — entry point (uses both packages to handle an order)
catalog/
  • __init__.py — re-exports from .products import get_price
  • products.py — implements get_price(name)
billing/
  • __init__.py — re-exports from .invoice import format_invoice
  • invoice.py — implements format_invoice(name, qty, unit_price)
catalog/ holds product data (products.py); billing/ holds invoice formatting (invoice.py). Each __init__.py re-exports the public API, so main.py can call out with two absolute imports: from catalog import get_price and from billing import format_invoice.
main.py Orchestrating Two Packages
catalogget_price()main.pyorchestrates bothbillingformat_invoice()from catalog import get_pricefrom billing import ...
main.py calls out with absolute imports to grab unit_price from catalog and to format the invoice via billing. Each package internally uses relative imports in __init__.py to re-export .products / .invoice.

Complete the attached catalog/ and billing/ packages including their __init__.py files, then orchestrate them from main.py with absolute imports (open each file from 📂 Files and save with Cmd+S or 💾).

① In catalog/products.py, implement get_price(name)"apple" → 100, "orange" → 150, otherwise 0 (a dict + dict.get(name, 0) is convenient).

② In catalog/__init__.py, write from .products import get_price so the outside can call from catalog import get_price (a relative import).

③ In billing/invoice.py, implement format_invoice(name, qty, unit_price) — compute qty * unit_price and return a string like "apple x 3 = $300".

④ In billing/__init__.py, write from .invoice import format_invoice.

⑤ In main.py, do from catalog import get_price and from billing import format_invoice (absolute imports). Order 3 of "apple", look up the unit price, format the invoice, and print().

Python Editor

Run code to see output

From here on is bonus material — rare in real code

The section below covers __all__ and is informational only. Real-world code almost never uses from package import *, so this part doesn't affect the main thread. As long as you've got the explicit-name import form down, feel free to skim through this section.

Controlling from package import * with __all__

When someone writes from my_package import * to pull everything in with a star, you can control what's exposed via __all__ in __init__.py. With __all__ = ["add"], the * import only picks up add — nothing else.

__all__ Narrows What's Exposed
__init__.pyfrom .calc importadd, multiply__all__ = ["add"]from pkgimport *add→ pulled inmultiply→ not pulled in(NameError)
With __all__ = ["add"] in __init__.py, only add comes through from package import *. multiply is left out (you can still grab it explicitly).
# my_package/__init__.py
from .calculation import add, multiply

__all__ = ["add"]   # only add is exposed via *

# Caller side
# from my_package import *
# add(1, 2)        # OK
# multiply(1, 2)   # NameError (not pulled in by *)

In Practice, Avoid * Imports

from my_package import * is hard to read — you can't tell at a glance what came in. Real-world code almost always uses explicit names like from my_package import add, multiply. Treat __all__ as a *safety net for the rare case someone does use ``**.

Verify __all__ behavior with the attached bundle/ package. Open bundle/__init__.py from 📂 Files — it pulls in both add and multiply but narrows the exposure with __all__ = ["add"].

① In main.py, do from bundle import * and print add(2, 3).

② Then wrap a call to multiply(2, 3) in try/except and verify it raises NameError (this exercise uses the CPython-compatible Pyodide runtime — first load takes 5–15 seconds).

Python Editor

Run code to see output

This article covered using __init__.py to bundle several modules into one package, the difference between from package import name and import package, picking between absolute and relative imports, and a folder layout close to a real project.

QUIZ

Knowledge Check

Answer each question one by one.

Q1Which file must you place in a folder for it to be recognized as a package?

Q2From my_app/utility/validator.py, which is the correct relative import to load helper.py in the same utility folder?

Q3What happens if you write a relative import (from . import xxx) inside an entry point (a file you run directly with python main.py)?