main.py1import notes2import file_store3s = file_store.Store()4notes.add(s, "milk")5print(notes.show(s))import db_stores = db_store.Store()notes.pyadd()show()inside_clean()saveloadnotes.txtfile_store.pynotes.dbdb_store.pymilkMilk
Blocks: modules, one Python file each (main.py, notes.py, file_store.py, db_store.py)Ports and wires: the public interface and the import links between modulesTokens: the module object import hands back, and one note travelling through a callA program split into modules: main.py (mint) with its code on top, notes.py (violet) with two public ports and a hidden _clean part, and a Storage port that any store with save and load fits. A milk token follows one call through add and _clean into the plugged store. Then file_store.py is pulled out and db_store.py goes into the same port: only two lines of main.py change, and notes.py is never touched. Python checks none of this while the program runs: any object with save and load works (duck typing). The Storage Protocol class is there for people and for type checkers such as mypy, which compare a store against it.

Split into modules

  1. A big program in one file is hard to read and change. So we split it into modules: Python files, each with one clear job. main.py starts the program, notes.py manages notes and file_store.py saves them.

Public and hidden

Interfaces

Good design

What happens

;; step 1/10 · modules
module   one .py file with one clear job
  main.py         starts the program
  notes.py        adds and shows notes
  file_store.py   saves notes in a file
  db_store.py     saves notes in a database
a module's name is its file name without .py

Python checks none of this while the program runs: any object with save and load works (duck typing). The Storage Protocol class is there for people and for type checkers such as mypy, which compare a store against it. The underscore and __all__ are conventions. Python still lets you write notes._clean(...) from outside; they tell other programmers what is safe to rely on.