3.1Modules, packages & standard library
The essentials in 30 seconds
- One module = one .py file, one package = one folder of modules: reuse via import instead of copy-pasting — from stats import frequence, alias import x as y.
- The standard library is the chest already paid for: pathlib, datetime, collections, itertools, argparse, sqlite3 — tested, documented, with no dependency to install.
- The __main__ block separates imported use from direct use: the demo only runs on direct execution, never on import — no more side effects.
Running mini-case — Nabil tidies his analysis scripts
Nabil, junior data analyst in Algiers, analyses sales with three scripts that copy the same cleaning functions: a fix in Algiers-centre, forgotten in the Oran copy, skews a report. He moves the shared code into a stats.py module (frequence function with Counter), imports via from stats import frequence, guards the demo with if __name__ == "__main__", and replaces his date tinkering with datetime and pathlib from the standard library. One codebase, three correct analyses.
This same case returns in the lesson, the diagram, the script and the 48h challenge.
1. Modules, packages and clean imports
When the project grows, putting everything in a single file becomes unmanageable: a module is a reusable .py file (import stats then stats.frequence()), a package is a folder grouping consistent modules. Import at the top of the file, grouped (standard → third-party → local), in absolute form (from monapp import stats), with the variants import math, from math import sqrt, import x as y — and never from x import *, which pollutes the namespace.
The if __name__ == "__main__": block makes a file both executable and importable: code under this block only runs when launched directly (python stats.py), not on import. Nabil puts his demo on a sales sample from Algiers there: imported, the module only provides functions; executed, it shows the example. Circular imports (A imports B which imports A) signal a split to rethink, not a trick to force.
2. The standard library: check before installing
Python ships with batteries included: pathlib (modern paths), datetime (dates and times), collections (Counter, defaultdict), itertools (iterations), argparse (command-line interfaces), sqlite3 (embedded database), plus os, sys, json, random, math. Before any pip install, Nabil checks the standard chest: counting sales by city with Counter, listing a folder with pathlib, dating an export with datetime.
Professional reflex: standard first, PyPI next. The standard library is tested, documented and dependency-free — each external library adds a cost (installation, versions, maintenance). Nabil only reaches for pip for what the standard library does not cover (tabular analysis, level-4 charts), and keeps his imports absolute and grouped to stay readable.
🔎 Concrete example — Sales stats in a guarded module
Nabil counts sales by city from a reusable module.
from collections import Counter
def frequence(contacts):
return Counter(c["ville"] for c in contacts)
if __name__ == "__main__":
echantillon = [{"ville": "Alger"}, {"ville": "Oran"}, {"ville": "Alger"}]
print(frequence(echantillon))
Reading: imported = functions only; run directly = demo on the sample.
Lesson: the __main__ block guards against side effects on import.
Section vocabulary
- Module
- A reusable .py file: access it with import mon_module, then mon_module.fonction().
Simple example: Nabil's stats.py imported by his three analyses.
What it is not: a copy-paste of the function in each script.
Reflex: move shared code into a module from the 2nd copy.
- Package
- A folder grouping consistent modules to structure a project.
Simple example: A monapp/ folder with stats.py and nettoyage.py.
What it is not: twenty .py files loose with no folder.
Reflex: group by theme, import in absolute form.
- import / from / as
- Loads reusable code: import math, from math import sqrt, import x as y; never from x import *.
Simple example: from stats import frequence at Nabil's.
What it is not: from stats import * which pollutes the namespace.
Reflex: import explicitly, at the top, grouped.
- Standard library
- Modules shipped with Python: datetime, pathlib, collections, itertools, argparse, sqlite3, os, json, random, math…
Simple example: Counter for cities, datetime to date Nabil's export.
What it is not: installing an external package for what Counter already does.
Reflex: check the standard library before pip install.
- if __name__ == "__main__"
- Block executed only on direct launch, not on import: separates the demo from the functions.
Simple example: Nabil's demo on the sample, silent on import.
What it is not: a test print that replays on every import.
Reflex: __main__ systematically for any demo.
In practice — Organise into modules and leverage the stdlib
- Create a “stats.py” module with two functions (e.g. frequence, moyenne), then import it into another file and call its functions.
- Explore the stdlib: datetime.now() to date an export, Counter to count cities, pathlib to list a folder.
- Use “from module import fonction” and the “import x as y” alias: compare the readability of both styles.
- Add an “if __name__ == '__main__'” block with a demo: check that it does not run on import.
Ready-to-say script — presenting your module (30 s)
“Shared code lives in stats.py: frequence function with Counter, imported via from stats import frequence, demo guarded under __main__. Dates and paths via datetime and pathlib from the standard library, absolute imports at the top, never a star. One codebase for three analyses.”
Say in review; show the import and the __main__ block.
Copy-paste template — module + guarded import
from collections import Counter
from datetime import datetime
from pathlib import Path
def frequence(contacts):
return Counter(c["ville"] for c in contacts)
def dater_export():
return datetime.now().strftime("%Y-%m-%d")
if __name__ == "__main__":
print(frequence([{"ville": "Alger"}]))
print(sorted(Path(".").iterdir()))
Independent
One stats.py module with Counter is enough to make monthly reports reliable.
SME in Algiers
Nabil shares his module between the Algiers-centre and Oran analyses with no copying.
Product team
Enforce grouped absolute imports and systematic __main__ for importable modules.
Beginner
This week: one module, two functions, one demo under __main__.
✓ Best practices
- Put a systematic __main__ block for any demo or manual test.
- Check the standard library before installing anything.
- Import in absolute, grouped form (standard → third-party → local), never with *.
✗ Common pitfalls
- Copy-pasting a function between files instead of making it a module.
- Using from x import * (overwritten names, unreadable origin).
- Tinkering with circular imports instead of re-splitting the modules.
Key takeaways
- MODULE = one .py file; PACKAGE = one folder of modules: import (from/as) instead of copying.
- The STANDARD LIBRARY covers dates, paths, counts, CLI, embedded database… with NOTHING to install.
- if __name__ == '__main__' separates EXECUTABLE code (demo) from IMPORTABLE code (functions).
- CLEAN imports: at the top, grouped, ABSOLUTE, never a star; circular = split to rethink.
48h challenge — apply tomorrow
Extract 1 duplicated function into a module imported by 2 scripts, with a demo under __main__.
- I tell a module (file) from a package (folder) apart.
- My imports are absolute, grouped, with no star.
- My demo under __main__ no longer runs on import.
Frequently asked questions
What is the difference between “import math” and “from math import sqrt”?
import math loads the whole module, to call with math.sqrt() (explicit origin). from math import sqrt imports only sqrt, callable directly. The first form reads better in a team; avoid from module import *, which pollutes the namespace and hides collisions.
What is “if __name__ == '__main__'” really for?
To make a file both executable and importable: code under this block only runs when launched directly (python fichier.py), not on import. Put the demo or entry point there — imported, the module only provides functions, with no side effects.
What happens with the if __name__ == "__main__" block on import?