Назад в блог
Бэкенд

Why Python Won, and What the Interpreter Is Doing While You Wait

Bytecode, reference counting, the GIL, and the free-threaded future — plus an honest account of when Python is the right tool and when it is the expensive one.

Опубликовано
Время чтения
10 мин чтения
Автор
Yakhya

Python did not win on speed. It won because the distance between an idea and a working program is shorter in Python than almost anywhere else, and because that shortness compounded into the largest scientific and machine learning ecosystem in software. Understanding how CPython executes your code explains both the appeal and the limits.

From source to bytecode

CPython compiles your source to bytecode — a compact instruction set for a stack machine — and caches it in __pycache__. There is no machine code involved (until 3.13's experimental JIT); a C loop called the evaluation loop reads instructions one at a time and executes them. You can look at the result directly, which is one of the best ways to build intuition about cost.

python
import dis

def total(items):
    return sum(x.price for x in items)

dis.dis(total)
# LOAD_GLOBAL   sum
# LOAD_CONST    <genexpr>
# MAKE_FUNCTION
# ...
# CALL 1
# RETURN_VALUE

Everything is an object, including integers, and every operation goes through the object protocol. Adding two numbers means unboxing two heap objects, dispatching through a type's method table, allocating a result. That indirection is the source of Python's slowness and also of its flexibility — the same machinery is what lets NumPy overload arithmetic to operate on entire arrays.

Memory: reference counting plus a cycle collector

Every object carries a refcount, incremented and decremented as references appear and vanish; at zero, it is freed immediately. That gives predictable, prompt cleanup — a file closes the moment its last reference goes away — but cannot handle cycles, so a generational garbage collector periodically sweeps for unreachable groups. Practical consequences: deleting a large structure frees memory right away, __del__ ordering is not something to depend on, and gc.freeze() before forking keeps copy-on-write pages shared in pre-fork servers like Gunicorn.

The GIL, precisely

The Global Interpreter Lock ensures exactly one thread executes Python bytecode at a time. It exists because refcounting is not thread-safe, and making every refcount atomic would slow single-threaded code substantially. The GIL is released around blocking I/O and inside C extensions that opt out of it — which is why threads are perfectly good for network calls and why NumPy, Pandas, and PyTorch achieve real parallelism despite it. The GIL only blocks parallel execution of pure-Python CPU work.

  • I/O-bound work → threads or asyncio. The GIL is not in your way.
  • CPU-bound pure Python → multiprocessing, or push the hot loop into NumPy, Cython, Numba, or Rust via PyO3.
  • Python 3.13+ ships an optional free-threaded build (PEP 703) with no GIL. It is real, it is improving, and it is not yet the default — treat it as a horizon, not a plan.

asyncio in one paragraph

async def creates a coroutine; awaiting it suspends the function and hands control back to an event loop that resumes it when the awaited operation is ready. It is cooperative concurrency in a single thread — one blocking call in an async handler stalls every other task in the process. The rule is simple and unforgiving: never call blocking code inside async code without wrapping it in a thread executor.

python
import asyncio, httpx

async def fetch_all(urls: list[str]) -> list[str]:
    async with httpx.AsyncClient(timeout=5.0) as client:
        tasks = [client.get(u) for u in urls]
        results = await asyncio.gather(*tasks, return_exceptions=True)
    return [r.text for r in results if not isinstance(r, Exception)]

Why it is still the right choice, often

For data work, machine learning, automation, scripting, and a very large share of web backends, the bottleneck is a database, a network, or a GPU — not the interpreter. Python's real speed is the speed of the team using it: readable code, an enormous standard library, and packages for essentially everything. Add type hints and mypy or pyright, and you get much of the safety people leave Python for, without leaving Python.

Where it stops being the right tool: latency-critical hot paths measured in microseconds, memory-constrained environments, heavy CPU-bound parallelism, and anything that must ship as a small self-contained binary. The mature pattern is not to switch languages but to draw the border deliberately — Python for orchestration, glue, and iteration speed; a compiled language for the twenty lines that actually need to be fast.

Теги
PythonCPythonGILPerformanceInterpreters
Продолжить чтениеВсе статьи
Yakhya
© 2026 Yakhya. Все права защищены.