Why Python Exists
Why Python Exists
Python exists because people wanted a language that reads like notes you would leave a colleague: obvious names, as little ceremony as possible, batteries included. The boring default in this book is the same default Python was built for: code a stranger can run on Monday morning.
Mental model
Python is an interpreted, dynamically typed language with garbage collection and a large standard library. You trade a compile step and static types-by-default for:
- a short path from idea to running program
- one obvious way to do many everyday jobs (files, JSON, HTTP, processes)
- a single runtime you can inspect (
type,dir,help) - exceptions you can see in the traceback
That trade is the point. If you keep reaching for magic metaclass frameworks on day one, you will dislike this book. If you like software that stays boring as it grows, you will like it.
A terminal is the window where you type commands. PATH is the list of directories the shell searches for a program named uv or python. A file path is a location on disk (hello.py, /home/you/desk).
Worked examples
Case 1: A complete program, no ceremony
Save as hello.py. This is the smallest useful Python program: a function, a print, and the usual main guard.
# hello.py
def main():
print("desk is open")
if __name__ == "__main__":
main()Run:
uv run python hello.pyOutput:
desk is open
uv run picks a Python (3.14 in this book) and runs the file. There is no compile-to-binary step you have to wait on. What you ran is the source.
The if __name__ == "__main__" line means “only call main when this file is the program,” not when another file imports it. Keep that guard. It is boring and it prevents accidents.
Case 2: Explicit failure, visible traceback
Python uses exceptions for failure. This program opens tables by number. Zero is nonsense, so it raises. The traceback names the file, the line, and the exception type.
# open_tables.py
def open_table(n):
if n <= 0:
raise ValueError(f"table {n}: number must be positive")
print(f"opened table {n}")
def main():
tables = [3, 0, 11]
for n in tables:
open_table(n)
if __name__ == "__main__":
main()Run:
uv run python open_tables.pyOutput (the process exits non-zero):
opened table 3
Traceback (most recent call last):
File "open_tables.py", line 16, in <module>
main()
File "open_tables.py", line 12, in main
open_table(n)
File "open_tables.py", line 4, in open_table
raise ValueError(f"table {n}: number must be positive")
ValueError: table 0: number must be positive
The line numbers in your traceback follow the file you saved. Read it from the bottom: the exception, then the call that caused it.
Case 3: Data that looks like the domain
Python’s built-in types are enough for a lot of desk work. This program models a ticket as a dict, then prints a label. Later chapters will replace the dict with a dataclass. Start here.
# ticket.py
def label(ticket):
return f"ticket {ticket['id']} → table {ticket['table']}"
def main():
t = {"id": 7, "table": 12}
print(label(t))
if __name__ == "__main__":
main()Run:
uv run python ticket.pyOutput:
ticket 7 → table 12
Case 4: What Python will let you do that you should not
This program works. It is also a waste: a class, a constructor, and a method whose only job is print.
# too_clever.py
class Printer:
def __init__(self, stream):
self.stream = stream
def emit(self, message):
print(message, file=self.stream)
def main():
import sys
Printer(sys.stdout).emit("desk is open")
if __name__ == "__main__":
main()Run:
uv run python too_clever.pyOutput:
desk is open
The boring version is Case 1. Introduce a class when you have state that lives and behavior that belongs to it — not when you have a slogan about objects.
The trap
Coming from a language that prizes types or from a tutorial that starts with a web framework, it is tempting to wrap every script in a package, a plugin system, and a settings object on day one. That is how a 20-line desk tool becomes a 12-module “platform.”
Write the function. Run it. Add structure when a name collides or a test needs a seam.
The boring rule
- Prefer a function and a dict or dataclass over a hierarchy.
- Raise a specific exception (
ValueError,TypeError) for the caller’s mistake. Do not return"error"strings as the main API. - Keep the
mainguard. - Split files when names collide or when a boundary is real.
- If the standard library already does the job, do not add a dependency yet.
Try this
- Change
hello.pyso it prints the number of tables (anint) next to the message. Use an f-string. - In
open_tables.py, catchValueErroraroundopen_table, print the error, and continue to the next table. - In
ticket.py, add astatuskey ("open"/"paid") and print it inlabel.