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 #
billing/
├── __init__.py
├── tax.py
└── invoices.py
main.py
# billing/tax.py
GST_RATE = 0.18
def add_tax(amount, rate=GST_RATE):
return round(amount * (1 + rate), 2)
# 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),
}
# 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()
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.pyoremail.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.