← All posts

July 15, 2026 · 3 min read

PythonInternals

The Secret Morning Routine of CPython

This is Part 2 of a three-part series on Python internals (Part 1: A Tour of the Python Engine). Based on CPython 3.9.

Before Python even looks at your first line of code, it goes through a complex "morning routine" called initialization. Like any good morning routine, it happens in a strict order, and skipping a step would mean the rest of the day falls apart — your program would crash before it ever ran.

The routine happens in three distinct phases.

Phase 1: Preinitialization

This is basic housekeeping — the equivalent of turning on the lights before doing anything else.

Python sets up how it will handle memory allocation, and it determines the encoding it will use to talk to your operating system — how bytes from the terminal, environment variables, and file paths should be interpreted as text. This sounds mundane, but it has to happen first: almost everything after this point allocates memory and reads configuration, and both need these ground rules established.

Phase 2: Core initialization

This is where Python builds its "brain." Two crucial data structures come to life:

  • The interpreter state — a structure that tracks everything global to the interpreter, including which modules have been loaded.
  • The thread state — a structure that tracks your code's current progress: what's executing, and where.

This phase also initializes the famous (and infamous) Global Interpreter Lock — the GIL. The GIL ensures that only one thread executes Python bytecode at a time, which keeps the engine's internal object bookkeeping safe without fine-grained locking on every object. It's the reason CPython threads don't give you parallel CPU-bound speedups — and seeing it born here, as a core piece of the engine's memory-safety strategy rather than an afterthought, makes the design trade-off much easier to understand.

Phase 3: Main initialization

Finally, Python gets ready for you:

  • It sets up sys.path — the list of directories Python searches when you import something. This is why imports "just work" for the standard library and your local files.
  • It initializes the builtins module — the reason you can call print(), len(), and range() without importing anything. They were imported for you, right here.
  • It creates a special module called __main__ — the namespace where your script will actually live. That module is the answer to one of Python's most-Googled idioms: if __name__ == "__main__": is literally asking "am I the script Python was launched with, or was I imported by someone else?"

Why this matters

Once you know the routine, several everyday mysteries dissolve. Why can embedding Python in a C application be fiddly? Because the embedder has to drive these phases correctly. Why does python -S (skip site module) start faster? Because you've trimmed part of the morning routine. Why do interpreter startup times matter for CLI tools? Because all of this runs before your first line does.

With the engine awake and caffeinated, it's ready to actually run something. That's Part 3: From Script to Screen: How Your Code Actually Runs.