Frontend Labs
⋯

Part I — Foundations: the Python way

Part I covers the language you need to read any Python program. Chapters 3–9 move quickly if you know another language. Chapter 2 (names and objects) is where Python differs from Go, C, and Rust, and it explains more production bugs than any other chapter. Read it slowly and run every snippet. Part I ends with a checkpoint and two projects.


1. What Python is: CPython, bytecode, and the REPL

1.1 The language and the interpreter

"Python" is a language specification. CPython is the reference implementation, written in C, and it is what you get from python.org, uv, Homebrew, and Docker's python images. Other implementations exist (PyPy with a JIT, MicroPython for microcontrollers), but the data and AI ecosystem assumes CPython, because NumPy and PyTorch are CPython extensions. In this guide, "Python" means CPython.

What happens when you run python app.py:

 app.py ──▶ tokenizer ──▶ parser ──▶ AST ──▶ compiler ──▶ bytecode ──▶ eval loop (C)
  text                                 tree               code objects    one bytecode
                                                          (cached in      instruction
                                                          __pycache__/    at a time
                                                          *.pyc)

There is a compiler, but it compiles to bytecode for a virtual machine, not to machine code. The VM is a loop in C (ceval.c) that executes one instruction at a time. Chapter 25 opens that loop. For now, two facts matter:

  1. Nothing is checked until the line runs. A typo in a function name inside an if branch that never executes is not an error. Linters and type checkers (Ch 18) exist to catch what the compiler does not.
  2. Every operation is dynamic. a + b looks up the type of a at run time and calls its __add__ method. That flexibility is the source of both Python's expressiveness and its slowness.

Since 3.11, CPython has a specializing interpreter that rewrites hot instructions for the types it actually sees (Ch 25), which made it 1.25× to 2× faster than 3.10. 3.13 added an experimental JIT and an experimental free-threaded build without the GIL; in 3.14 the free-threaded build is officially supported (Ch 27).

1.2 The REPL: your most important tool

python3.13          # or: uv run python
>>> 2 ** 100
1267650600228229401496703205376
>>> name = "Ada"
>>> f"hello, {name}"
'hello, Ada'
>>> help(str.split)      # documentation for any object
>>> dir("x")             # every attribute a string has
>>> type(3.0), type([]), type(len)
(<class 'float'>, <class 'list'>, <class 'builtin_function_or_method'>)

Since 3.13 the REPL has colour, multi-line editing, and paste mode (F3). Use it constantly: whenever you are unsure what an expression returns, ask the interpreter instead of guessing. In data work, Jupyter notebooks (Ch 62) are the same idea with saved outputs and plots.

1.3 Your first program

# hello.py
import sys

def main() -> int:
    who = sys.argv[1] if len(sys.argv) > 1 else "world"
    print(f"Hello, {who}!")
    return 0

if __name__ == "__main__":
    raise SystemExit(main())
$ python3 hello.py Linux
Hello, Linux!
$ echo $?
0

Every piece of this shape matters:

  • sys.argv is the argument list, as in C's argv (OS Ch 3).
  • if __name__ == "__main__": runs main() only when the file is executed, not when another module imports it (Ch 9).
  • raise SystemExit(main()) turns main's return value into the process exit code, which shells, systemd, and Kubernetes read.
  • -> int is a type hint. Python ignores it at run time; tools read it.

Indentation is syntax. Blocks are defined by indentation (4 spaces, by convention and by ruff format), not braces. Mixing tabs and spaces is an error.


Generated from real-life-python-guide.md · 8 Oct 2026 16:36