Why Python Exists

Updated

September 8, 2026

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.py

Output:

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.py

Output (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.py

Output:

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.py

Output:

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 main guard.
  • 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

  1. Change hello.py so it prints the number of tables (an int) next to the message. Use an f-string.
  2. In open_tables.py, catch ValueError around open_table, print the error, and continue to the next table.
  3. In ticket.py, add a status key ("open" / "paid") and print it in label.