What Is Python
What Is Python
After reading this chapter, you will understand how Python source code compiles to bytecode, executes inside the CPython virtual machine, and leverages the Python 3.14 free-threaded runtime architecture.
Mental model
Python is an interpreted, strongly and dynamically typed language executed by a virtual machine. When you run a script, the standard CPython runtime does not interpret raw human-readable text line-by-line in real time. Instead, it parses source text into an Abstract Syntax Tree (AST), compiles the AST into a sequence of stack-based bytecode instructions, and executes those instructions inside an evaluation loop:
[ Your Code: hello.py ]
│
▼
┌───────────────┐
│ Tokenizer & │ Transforms source text into grammar tokens
│ Parser │
└───────┬───────┘
│ AST (Abstract Syntax Tree)
▼
┌───────────────┐
│ Compiler │ Emits bytecode instructions & code objects (.pyc)
└───────┬───────┘
│ Bytecode (e.g. LOAD_CONST, BINARY_OP, RETURN_VALUE)
▼
┌──────────────────────────────────────────────────────────┐
│ CPython Virtual Machine (ceval.c) │
│ │
│ ┌─────────────────┐ ┌──────────────────────────────┐ │
│ │ Evaluation Loop │──▶│ PyFrameObject (Value Stack) │ │
│ └────────┬────────┘ └──────────────────────────────┘ │
│ ▼ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Memory Allocator (pymalloc) & Cyclic GC │ │
│ └────────────────────────────────────────────────────┘ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Python 3.14 Runtime: GIL-less Free-Threading │ │
│ │ (PEP 703: multi-core true parallel execution) │ │
│ └────────────────────────────────────────────────────┘ │
└────────────────────────────┬─────────────────────────────┘
▼
[ Host OS / Hardware CPU ]
Every entity in Python—an integer, a string, a function, or a class—is represented internally as a heap-allocated C structure called a PyObject.
Minimal example
Save the following file as sys_inspect.py. It inspects your active Python implementation, execution flags, and runtime environment:
# sys_inspect.py
import platform
import sys
def main() -> None:
print(f"Implementation : {platform.python_implementation()}")
print(f"Python Version : {sys.version.split()[0]}")
print(f"Compiler : {platform.python_compiler()}")
print(f"Platform Arch : {platform.machine()} ({sys.platform})")
print(f"Bytecode Magic : {sys.implementation.cache_tag}")
print(f"Float Info : max={sys.float_info.max:.2e}, epsilon={sys.float_info.epsilon:.2e}")
if __name__ == "__main__":
main()Run this file using uv:
uv run python sys_inspect.pyOutput:
Implementation : CPython
Python Version : 3.14.0
Compiler : GCC 14.2.0 (or Clang)
Platform Arch : x86_64 (linux)
Bytecode Magic : cpython-314
Float Info : max=1.80e+308, epsilon=2.22e-16
Worked examples
1. Peeking inside the CPython compiler with dis
To see what the CPython virtual machine actually executes, you disassemble functions into bytecode using the standard library dis module.
Save this file as disassemble_demo.py:
# disassemble_demo.py
import dis
def compute_total(base_price: float, tax_rate: float) -> float:
discount = 5.0
final_price = (base_price - discount) * (1.0 + tax_rate)
return final_price
def main() -> None:
print("=== Disassembly of compute_total ===")
dis.dis(compute_total)
if __name__ == "__main__":
main()Run the script:
uv run python disassemble_demo.pyOutput:
=== Disassembly of compute_total ===
4 0 LOAD_CONST 1 (5.0)
2 STORE_FAST 2 (discount)
5 4 LOAD_FAST 0 (base_price)
6 LOAD_FAST 2 (discount)
8 BINARY_OP 10 (-)
12 LOAD_CONST 2 (1.0)
14 LOAD_FAST 1 (tax_rate)
16 BINARY_OP 0 (+)
20 BINARY_OP 5 (*)
24 STORE_FAST 3 (final_price)
6 26 LOAD_FAST 3 (final_price)
28 RETURN_VALUE
Why this matters: CPython is a stack machine. LOAD_FAST pushes a local variable onto the evaluation stack, LOAD_CONST pushes a constant literal, BINARY_OP pops the top operands, executes the arithmetic in C, and pushes the result back onto the stack. Understanding this stack model demystifies performance profiling later in this book.
2. Inspecting runtime execution frames
While your code is executing, CPython maintains a call stack of frame objects (PyFrameObject). Each frame encapsulates the code object being executed, the local variable array, the global namespace dictionary, and the instruction pointer.
Save this file as frame_demo.py:
# frame_demo.py
import inspect
import sys
def nested_worker(task_id: int, payload: str) -> None:
# Inspect the current active execution frame
current_frame = sys._getframe()
print(f"Current Function : {current_frame.f_code.co_name}")
print(f"Current File : {current_frame.f_code.co_filename}")
print(f"Current Line Number: {current_frame.f_lineno}")
print(f"Local Names : {list(current_frame.f_locals.keys())}")
# Inspect the caller's frame
caller_frame = current_frame.f_back
if caller_frame:
print(f"Caller Function : {caller_frame.f_code.co_name}")
print(f"Caller Locals : {caller_frame.f_locals}")
def supervisor() -> None:
job_batch = "batch-alpha"
nested_worker(task_id=101, payload=job_batch)
if __name__ == "__main__":
supervisor()Run the script:
uv run python frame_demo.pyOutput:
Current Function : nested_worker
Current File : frame_demo.py
Current Line Number: 8
Local Names : ['task_id', 'payload', 'current_frame']
Caller Function : supervisor
Caller Locals : {'job_batch': 'batch-alpha'}
Why this matters: Tracebacks, debuggers (like pdb), and profilers are not magic—they walk this linked list of f_back frame references.
3. Checking Python 3.14 free-threading (GIL status)
Python 3.14 incorporates the milestone implementation of PEP 703 (making the Global Interpreter Lock optional). When built with free-threading enabled, Python can run multiple threads across physical CPU cores simultaneously without being serialized by a global mutex.
Save this file as gil_status.py:
# gil_status.py
import sys
def check_threading_runtime() -> None:
# sys._is_gil_enabled is available in free-threaded CPython builds
is_gil_fn = getattr(sys, "_is_gil_enabled", None)
print("=== Python 3.14 Concurrency Architecture ===")
if is_gil_fn is not None:
gil_active = is_gil_fn()
status = "ENABLED (Serialized threads)" if gil_active else "DISABLED (True multi-core parallelism)"
print(f"Global Interpreter Lock (GIL): {status}")
else:
print("Global Interpreter Lock (GIL): Active (Standard CPython build)")
print(f"Recursion Limit : {sys.getrecursionlimit()}")
print(f"Thread Switch Interval : {sys.getswitchinterval():.4f}s")
if __name__ == "__main__":
check_threading_runtime()Run the script:
uv run python gil_status.pyOutput:
=== Python 3.14 Concurrency Architecture ===
Global Interpreter Lock (GIL): ENABLED (Serialized threads)
Recursion Limit : 1000
Thread Switch Interval : 0.0050s
Pitfalls
1. Thinking Python is “just interpreted”
The Trap: Believing Python reads lines as raw strings while executing, and assuming .pyc files are native machine code.
The Reality: Python compiles source code to bytecode first. If the file has not changed, Python reads the bytecode cached in __pycache__/*.pyc directly to save startup compilation time. Bytecode is instructions for the CPython VM, not machine code for x86_64 or ARM.
2. Modifying objects during iteration
The Trap: Deleting or inserting items into a list while looping over it.
# BROKEN
items = [1, 2, 3, 4, 5]
for x in items:
if x % 2 == 0:
items.remove(x) # Skips elements due to shifted indices!The Fix: Iterate over a shallow copy or use a comprehension:
# CORRECT
items = [x for x in items if x % 2 != 0]3. Mixing tabs and spaces
The Trap: In Python, indentation defines block scope. Mixing a tab character with spaces causes an immediate TabError at the parser level before any code runs. Always configure your editor to insert 4 spaces per indent.
Exercises
- Modify
sys_inspect.pyto also displaysys.byteorder(big-endian vs. little-endian) andsys.api_version. - Write a script
disassemble_loop.pythat defines a function with aforloop, disassembles it withdis.dis(), and identifies the opcode responsible for jumping back to the beginning of the loop (JUMP_BACKWARDorFOR_ITER). - Using
sys.getrefcount(), write a scriptrefcount_check.pythat displays the reference count of an empty list when created, when assigned to a second variable name, and afterdelis called on the second variable. - Verify your local Python 3.14 build flags by running
uv run python -c "import sysconfig; print(sysconfig.get_config_vars('Py_DEBUG', 'Py_GIL_DISABLED'))".
Further reading
- Official Documentation: CPython Internal Design & Architecture Notes (
https://docs.python.org/3.14/c-api/index.html). - PEP 703: Making the Global Interpreter Lock Optional in CPython (Sam Gross).
- PEP 659: Specializing Adaptive Interpreter (Faster CPython Project).
- Book: CPython Internals: Your Guide to the Python 3 Interpreter by Anthony Shaw.