PythonBeginner 13 min Lesson 10 of 30

Day 10 — Modules and Packages

Split code across files, import what you need, understand packages and __init__.py, and learn why the if __name__ == "__main__" line exists.

Python · Lesson 10 of 30
0/30 done(0%)

What is it? #

A module is just a Python file. The moment you have two files and one imports the other, you are using modules.

A package is a folder of modules. Import them with dots that mirror the folder structure: from billing.invoices import build_invoice.

Importing a module runs it, top to bottom, the first time. That is why import-time side effects (printing, connecting to a database, starting a server) are a bad idea — they fire whenever anyone imports the file, including your tests.

The if __name__ == "__main__": line exists to separate "run this file directly" from "import this file". It is the standard way to give a module a command-line entry point without hurting importers.

Think of it like this #

Modules are the drawers in a toolbox. All the screwdrivers in one, all the electrical bits in another. You do not tip everything onto the floor to find a screwdriver — you open the one drawer you need. Importing the whole contents of a drawer into your workspace with from x import * is exactly that mess.

Simple example #

A small billing app grows past one file. You split it into a package: a module for tax rules, a module for building invoices, and a main script that ties them together and can also be run by itself.

Code #

TEXT
billing/
├── __init__.py
├── tax.py
└── invoices.py
main.py
PYTHON
# billing/tax.py
GST_RATE = 0.18

def add_tax(amount, rate=GST_RATE):
    return round(amount * (1 + rate), 2)
PYTHON
# billing/invoices.py
from .tax import add_tax          # relative import, same package

def build_invoice(customer, amount):
    return {
        "customer": customer,
        "amount": amount,
        "payable": add_tax(amount),
    }
PYTHON
# main.py
from billing.invoices import build_invoice
import json

def run():
    invoice = build_invoice("Ravi", 1000)
    print(json.dumps(invoice, indent=2))

if __name__ == "__main__":
    run()
BASH
python main.py

How it works #

__init__.py marks the folder as a package. It can be empty; its presence is what matters for a traditional package layout, and it is also where you can re-export the names you want people to use.

from .tax import add_tax is a relative import — the leading dot means "from this same package". It keeps internal wiring independent of where the package sits in a larger project.

from billing.invoices import build_invoice pulls one name into main.py. Importing the specific function rather than the whole module makes the dependency obvious at the top of the file.

import json brings in a standard library module. Nothing to install — the standard library ships with Python, and it covers JSON, dates, files, HTTP, math and much more.

if __name__ == "__main__": is the important idiom. When you run a file directly, Python sets its __name__ to "__main__". When the same file is imported, __name__ is the module name instead. So the guarded code runs only when the file is the entry point, and importing main in a test does not fire off your program.

Real-world use #

Every real project is a package tree. The structure usually mirrors the domain: users/, billing/, notifications/, each holding the models, logic and helpers for that area. Day 28 covers a full project layout.

Import discipline shows up in reviews for a reason. Circular imports — module A imports B while B imports A — are one of the most annoying failures to untangle, and they usually mean two modules are really one concern that should be split differently.

The __main__ guard is what makes a module both a library and a script. Management commands, migration scripts and one-off data fixes all use it.

Common mistakes #

  • Using from module import *. It dumps unknown names into your file and makes it impossible to tell where anything came from.
  • Putting code with side effects at module level. It runs on import, including during tests.
  • Naming a file after a standard library module, like json.py or email.py. Your file shadows the real one and produces baffling errors.
  • Creating circular imports. If A and B need each other, move the shared part into a third module.
  • Forgetting the __main__ guard, so importing a script immediately executes it.

Practice #

Create a package called geometry with two modules: areas.py (functions for circle and rectangle area) and shapes.py (a function that takes a list of shape dictionaries and returns the total area, importing from areas). Add a main.py that prints a result and only runs when executed directly.

Quick quiz

  1. 1. What is a Python module?

  2. 2. What happens the first time a module is imported?

  3. 3. What does `if __name__ == "__main__":` do?

  4. 4. Why avoid `from module import *`?

  5. 5. What does the dot mean in `from .tax import add_tax`?

Summary

  • A module is a file and a package is a folder of modules with an `__init__.py`.
  • Importing runs the module once, so keep side effects out of module level.
  • Import specific names rather than using star imports.
  • Relative imports with a leading dot keep a package self-contained.
  • The `__main__` guard lets one file be both a script and an importable module.