The Story of Python
Python should not have won. By the conventional metrics of language design it is slow, it is dynamically typed in an era that rediscovered the value of static checking, and at the center of its reference implementation sits a single mutex — the Global Interpreter Lock — that prevents the very multicore parallelism the hardware industry spent two decades building toward. It went through a version transition so disruptive that it split the community for a decade and is remembered as a cautionary tale taught in software-engineering courses. Its packaging story was, for most of its life, a confusing thicket of distutils, easy_install, setuptools, eggs, and pip that drove newcomers to despair. And yet Python is, by most rankings in 2026, the most popular programming language in the world, the lingua franca of data science and artificial intelligence, the first language taught in a majority of universities, and the glue that holds together a staggering fraction of the software people actually ship.
The explanation is not that Python is fast or safe or elegant in the way a type theorist means elegant. The explanation is that Python optimized relentlessly, from its first day, for the one resource that turned out to matter most: human attention. Guido van Rossum designed a language you could read like pseudocode, write without ceremony, and learn in an afternoon, and then he and a community spent thirty-five years making sure the hard, fast, specialized work — the numerical kernels, the tensor math, the I/O — could be done in C and Fortran underneath while the human stayed in the readable layer on top. Python did not win by being the best tool for any single job. It won by being a good-enough tool for almost every job and the best tool for the meta-job of orchestrating all the others. This is the story of how a hobby project named after a comedy troupe became the substrate of the AI age, and of the trade-offs — the GIL, the packaging mess, the brutal 2-to-3 migration — that it survived along the way.
Christmas 1989: ABC’s Better-Behaved Child
In December 1989, Guido van Rossum, a researcher at the CWI institute in Amsterdam, was looking for a project to occupy himself over a quiet Christmas week when his office was closed. He wanted a hobby that would keep his mind busy, and he decided to write an interpreter for a new scripting language — one descended from a language called ABC that he had helped implement earlier in the 1980s. The first public release, Python 0.9.0, went out to the Usenet group alt.sources in February 1991. He named it after Monty Python’s Flying Circus, not the snake; the comedic spirit survives in the documentation’s spam and eggs to this day.
ABC is the key to understanding Python’s character. ABC was a teaching language, designed at CWI to be approachable for beginners, and it had several ideas Guido loved: it used indentation to delimit blocks rather than braces or begin/end, it had high-level built-in data structures, and it prized readability. But ABC was a closed world — monolithic, hard to extend, unable to talk to the operating system or to C libraries, and it had failed to gain adoption partly because of that isolation. Python was Guido’s attempt to keep ABC’s readability and indentation while fixing its fatal flaw: Python was designed from the start to be extensible, to call into C, to interact with the Unix environment it grew up in. That heritage runs straight back to the story of C and the story of Unix — Python was conceived as a high-level glue layer over a world built in C, and it never forgot it.
The indentation decision is the most visible thing about Python and the one newcomers argue about most. In Python, whitespace is syntax: the body of a loop or function is defined by its indentation level, not by curly braces. This is not a gimmick. It enforces the thing every other language merely encourages — that the visual structure of the code matches its logical structure — by making it impossible for them to disagree. You cannot write a Python block that looks nested but isn’t, because looking nested is being nested.
|
|
Batteries Included and the PEP Process
Two cultural decisions shaped Python at least as much as any syntax choice. The first is the “batteries included” philosophy: the standard library ships with a large, useful collection of modules out of the box — HTTP clients and servers, JSON and CSV and XML parsers, SQLite, regular expressions, subprocess control, an email library, a unit-test framework, cryptographic primitives. The idea was that for a vast range of ordinary tasks you should be able to reach for the standard library and get something done without installing anything. For most of Python’s life this was an enormous competitive advantage; in the era before good package managers, having urllib and json and os already present meant you could write a useful script on a fresh install.
The second is the PEP process. A Python Enhancement Proposal is a design document — modeled loosely on the IETF’s RFCs — that describes a proposed feature, change, or process, along with its rationale and the discussion around it. PEP 1 defines the process itself; PEP 8 is the famous style guide that standardized how Python code should look; PEP 20, “The Zen of Python,” encodes the language’s aesthetic in aphorisms (“Readability counts,” “There should be one — and preferably only one — obvious way to do it,” “Explicit is better than implicit”). You can read it at any prompt:
|
|
The PEP process gave the community a transparent, written, debatable mechanism for evolving the language, and it scaled the design conversation far beyond what one person could hold in their head — which mattered, because for nearly three decades one person held the final say.
The Benevolent Dictator For Life
From the beginning, Guido van Rossum was Python’s final decision-maker, and the community gave him a half-joking, half-serious title: BDFL, Benevolent Dictator For Life. The term originated in the Python community in the mid-1990s and spread from there to describe similar roles in other open-source projects. The arrangement was simple: anyone could propose, anyone could argue, the community discussed on mailing lists and in PEPs, but when consensus failed, Guido decided. He could accept or reject any PEP. He was the tiebreaker and the taste-maker, the one who kept the language coherent and resisted the feature creep that bloats languages run by committee.
For most of Python’s history this worked remarkably well. A single person with strong, consistent taste is a good way to keep a language feeling like one language rather than a pile of compromises. The cost is that it does not scale emotionally — the dictator absorbs every controversy personally — and it has no defined succession plan. Both of those costs came due in 2018, as we will see. But for the formative decades, the BDFL model gave Python something rare: a clear, opinionated vision, defended by someone with the authority to say no.
Python 3.0: The Decade-Defining Break
In December 2008, Python 3.0 was released, and it was deliberately not backward compatible with Python 2. This was the single most consequential decision in the language’s history. Guido and the core team had accumulated a list of design mistakes — wrong defaults, inconsistent behavior, the increasingly painful handling of text in a Unicode world — that could not be fixed without breaking existing code. Rather than carry the mistakes forever, they made one coordinated break and accepted the migration pain. The result defined Python’s entire next decade.
The flagship changes were small to state and enormous in aggregate:
| Aspect | Python 2 | Python 3 |
|---|---|---|
print |
statement: print "hello" |
function: print("hello") |
| Text / bytes | str is bytes; unicode is separate |
str is Unicode text; bytes is separate |
| Source default encoding | ASCII | UTF-8 |
| Integer division | 5 / 2 == 2 |
5 / 2 == 2.5 (use // for floor) |
range, dict.keys(), map, zip |
return lists | return lazy iterators/views |
xrange |
exists | gone; range is lazy |
| Exception syntax | except E, e: |
except E as e: |
| Comparing unlike types | allowed (1 < "a") |
raises TypeError |
input() |
raw_input() for strings |
input() returns a string |
The print change is the meme, but the str/bytes split is the one that actually mattered and the one that made the migration hard. In Python 2, str was a sequence of bytes that the language cheerfully pretended was also text, with unicode bolted on as a separate type; programs worked fine on ASCII and silently corrupted on anything else. Python 3 drew a hard, explicit line: str is a sequence of Unicode code points, bytes is a sequence of bytes, and you must encode to go from one to the other and decode to come back, choosing an encoding every time.
|
|
This is unambiguously correct design — text and bytes are different things — but it broke essentially every program that touched I/O, networking, or files, because those programs had been relying on the old confusion. Code that “just worked” in Python 2 now raised TypeError and UnicodeDecodeError everywhere the old laxity had been hiding bugs.
The Long Migration
Because Python 3 broke compatibility, the community faced a coordination problem of historic proportions: millions of lines of Python 2 code, thousands of libraries, and an entire ecosystem that could not move until its dependencies moved, which could not move until their dependencies moved. The result was a migration that took roughly twelve years, from the 2008 release of Python 3.0 to the official end-of-life of Python 2.7 on January 1, 2020.
The tooling built to bridge the gap is its own small history of pragmatism. The 2to3 tool automatically rewrote Python 2 source into Python 3, mechanically converting print statements to calls and the like — useful for a one-time port, useless for code that had to keep running on both. The __future__ module let a Python 2 file opt into individual Python 3 behaviors early, so you could write forward-compatible code:
|
|
And the six library (named because two times three is six) provided a compatibility layer of functions and constants that smoothed over the differences, letting a single codebase run unmodified on both versions — the standard approach for libraries that had to support the whole community during the long overlap.
The migration is the great cautionary tale of language evolution, and the lesson the rest of the industry drew from it was blunt: never break backward compatibility at this scale again. The pain was real, the fragmentation was real, and “still on Python 2” was a meaningful description of a stuck codebase for the better part of a decade. And yet — this is the crucial part — Python came out the other side stronger. The clean str/bytes model, the lazy iterators, the consistent division, the saner Unicode story made Python 3 a genuinely better language, and the ecosystem that emerged after 2020 was unified, modern, and unencumbered by the legacy. The migration was a near-death experience that, in the end, the language survived and was improved by. The full mechanics of how that ecosystem is built and shipped — the packaging story that grew up alongside this transition — is its own saga, told in Python packaging and distribution.
The Scientific Stack Capture
While the core team was fighting the 2-to-3 war, something more important was happening on top of Python: it was quietly being adopted as the default language of scientific and numerical computing, a domain Fortran, MATLAB, and IDL had owned. This capture is the single biggest reason Python matters today, and it happened because of one technical insight repeated across a dozen libraries: keep the human in slow, readable Python, and do the actual number-crunching in fast, compiled C and Fortran underneath.
The keystone is array computing. In the early 2000s two competing array libraries, Numeric and numarray, split the community; in 2005 Travis Oliphant unified them into NumPy, which gave Python a single, fast, n-dimensional array object (ndarray) backed by contiguous C memory and vectorized operations that run in compiled code. On top of NumPy grew the rest of the stack: SciPy (optimization, integration, signal processing, linear algebra), matplotlib (plotting, consciously modeled on MATLAB’s interface to ease migration), IPython and later the Jupyter notebook (interactive, literate computing in the browser), and pandas (Wes McKinney’s DataFrame, which brought R-style labeled, tabular data manipulation to Python and became the workhorse of data analysis).
Data scientist writes readable Python / Jupyter
|
+-----------+-----------+-----------+-----------+-----------+
| matplotlib| pandas | SciPy |scikit-learn| PyTorch |
| (plots) |(dataframes|(algorithms| (classic |(autograd, |
| | | | ML) | tensors) |
+-----------+-----------+-----------+-----------+-----------+
|
NumPy (ndarray, vectorized ops)
|
C / Fortran / BLAS / LAPACK / CUDA kernels
|
CPU / GPU hardware
The architecture in that diagram is the whole trick. The scientist lives in the readable top layer; the floating-point work happens in BLAS and LAPACK and CUDA at the bottom, where Python never treads. Python is the orchestration language, the notebook, the glue — and being slow at the language level simply does not matter when 99% of the runtime is spent inside a vectorized C kernel. Python turned its greatest weakness (interpreter speed) into a non-issue by ensuring the hot loops were never written in Python at all.
The AI and Machine Learning Capture
The scientific stack laid the foundation; machine learning built the skyscraper. scikit-learn, built on NumPy and SciPy, gave Python a clean, consistent API for classical machine learning — a uniform fit/predict/transform interface across dozens of algorithms — and became the standard teaching and production tool for everything short of deep learning. Then deep learning arrived, and the frameworks that defined it were Python-first.
TensorFlow, released by Google in 2015, and PyTorch, released by Facebook (Meta) in 2016, both chose Python as their primary interface for the same reason NumPy did: researchers needed to express models quickly and readably, and the tensor math would run on C++ and CUDA underneath. PyTorch in particular, with its eager, Pythonic, define-by-run style and autograd automatic differentiation, became the dominant framework of the research community and then of industry. By the era of large language models, essentially every major model — the transformers that power modern AI — was trained and served through Python frameworks. The most computationally intensive software humanity has ever run is driven, at the top, by a language whose interpreter holds a global lock and runs bytecode one operation at a time.
That is not a paradox; it is the same pattern at planetary scale. Python is the steering wheel; CUDA kernels on GPU clusters are the engine. The language won the AI era not by being fast but by being the most pleasant place from which to command things that are.
The GIL: The Lock at the Center
The honest accounting of Python’s trade-offs has to start with the Global Interpreter Lock. CPython, the reference implementation, protects its internal state — most importantly the reference counts it uses for memory management — with a single mutex. The consequence is stark: in CPython, only one thread executes Python bytecode at a time. You can create many threads, but they take turns holding the GIL, so a pure-Python, CPU-bound workload gets no speedup from multiple cores. The multicore revolution that reshaped hardware from the mid-2000s on largely passed CPython’s threading model by.
The GIL is not stupidity; it is a trade-off that was correct when it was made. A single global lock makes the interpreter simple, makes single-threaded code fast (no fine-grained locking overhead), and makes writing C extensions far easier because the extension author can assume the GIL is held and not worry about concurrent access to Python objects. The community lived with it for thirty years using workarounds: the multiprocessing module sidesteps the GIL by running separate processes (each with its own interpreter and lock) instead of threads; I/O-bound code uses threads or asyncio happily because the GIL is released during blocking I/O; and the CPU-heavy numerical libraries release the GIL while they run their C kernels, which is why NumPy and PyTorch parallelize fine despite it.
But “use processes” and “do the heavy work in C” are workarounds, not solutions, and the pressure to remove the GIL never went away. PEP 703, accepted in 2023, charts the path to free-threaded CPython — a build of the interpreter, available experimentally starting in Python 3.13 (2024), that removes the GIL and allows true multithreaded parallelism, using biased reference counting and other techniques to keep memory management correct without the global lock. It is being rolled out cautiously, as an opt-in build, because making it the default risks slowing down single-threaded code and breaking the enormous body of C extensions that assumed the GIL existed. After three decades, the lock at the center of Python is finally, carefully, being pried loose.
Trade-offs, Honestly
Beyond the GIL, the honest ledger includes two more entries. The first is raw speed: CPython is an interpreter that executes bytecode in a loop, and for tight, pure-Python numerical loops it can be one to two orders of magnitude slower than C. The mitigations are the whole point of the ecosystem — push hot code into NumPy, into a C extension, into Cython, into a JIT — and the CPython team has spent recent releases (the “Faster CPython” project from 3.11 onward, plus an experimental JIT in 3.13) clawing back interpreter performance. Languages like Mojo take the more radical bet of keeping Python’s syntax while compiling to native speed. But the bare interpreter remains slow, and pretending otherwise helps no one.
The second is the historically painful packaging story. For much of Python’s life, getting third-party code installed and isolated was a maze — distutils, then easy_install and eggs, then setuptools and pip and wheel, then virtual environments layered on top, then the modern wave of pyproject.toml, poetry, pdm, and uv. The modern baseline is far better than it was; the standard isolation-and-install workflow today is clean enough to fit in four lines:
|
|
Set against these costs is the thing that actually won: ergonomics and ecosystem. Python is the language people reach for to automate a server, parse a log, train a model, build a web backend, or teach a freshman, and the same readability that made it a good teaching language makes it a good production language for teams who value being able to understand their code six months later. That breadth — from a sysadmin’s throwaway script to a frontier AI model — is covered in domains like Python for DevOps, and it is the same language all the way up. The trade-offs are real; the ecosystem won anyway.
The Walrus and the Resignation
In July 2018, Guido van Rossum stepped down as BDFL. The proximate cause was PEP 572, which added the “walrus operator,” :=, an assignment expression that lets you bind a value to a name as part of a larger expression:
|
|
PEP 572 was technically minor — a small piece of syntactic sugar — but the debate around it was bitterly divisive, dragging through long, exhausting threads on the mailing lists. Guido championed and ultimately accepted the proposal, and the controversy, the personal toll of being the single human absorbing every argument, was the breaking point. In a message titled “Transfer of power,” he announced he was stepping down as Benevolent Dictator For Life. He pointedly declined to name a successor — “I am not going to appoint a successor” — and left the community to decide how to govern itself.
What replaced him was, fittingly for a community built on the PEP process, a written governance model chosen by vote. After considering several options laid out in competing PEPs, the community adopted PEP 13 and elected a Steering Council of five members to lead the language collectively, with new councils elected after each release cycle. Guido remained involved as an ordinary core developer (and was elected to early councils), but the era of one-person rule was over. Python had outgrown the model that built it. The thing that is striking, in hindsight, is how smoothly the transition went: a project that had depended for nearly thirty years on a single person’s judgment moved to elected, term-limited governance without a schism. That it could is itself a measure of how strong the culture — the PEPs, the Zen, the norms of written, transparent argument — had become.
Verdict
Python is the most successful “wrong” language in computing history, and the apparent contradiction dissolves the moment you stop measuring the wrong thing. By the metrics a language designer reaches for first — execution speed, type safety, true parallelism — Python is mediocre: it is slow, dynamically typed, and shackled until very recently to a global lock that wasted the multicore era. But those were never the metrics that decided adoption. Python optimized for human readability and for the ability to orchestrate fast code written in other languages, and those two bets compounded into total victory in exactly the domains — scientific computing, data analysis, machine learning — that came to define the 2010s and 2020s. The slowness stopped mattering because the hot loops moved to C and CUDA; the language stayed in the readable layer where humans live.
The trade-offs were real and the scars are visible. The 2-to-3 migration was a self-inflicted decade of fragmentation that the whole industry now studies as the canonical example of how not to break compatibility, and Python survived it only because the destination — a clean Unicode model and a coherent modern language — was genuinely worth the trip. The GIL is being removed thirty years late and cautiously, because the ecosystem that grew up around it cannot simply be told the rules changed. The packaging story is finally good but spent two decades being bad. And the BDFL model that gave Python its coherent taste also concentrated every controversy onto one person until a minor piece of syntax cost the community its founder’s leadership. None of that stopped it. A hobby project started over a Christmas break to keep one researcher entertained became the language in which humanity now writes its artificial intelligence, taught the most, deployed the widest, and reached for first — not because it was the best at any one thing, but because it was readable, extensible, and humble enough to let faster languages do the work it was bad at. That humility, encoded from ABC’s teaching heritage all the way to “Readability counts,” is the whole secret. Python won by being the language people wanted to think in, and then making sure thinking was enough.
Sources
- The History of Python (Guido van Rossum’s blog)
- A Brief Timeline of Python — Guido van Rossum
- Python 0.9.0 release announcement (1991) — alt.sources
- What’s New In Python 3.0 — official documentation
- Sunsetting Python 2 — Python Software Foundation
- PEP 8 — Style Guide for Python Code
- PEP 20 — The Zen of Python
- PEP 572 — Assignment Expressions (the walrus operator)
- PEP 13 — Python Language Governance
- PEP 703 — Making the Global Interpreter Lock Optional in CPython
- Transfer of Power — Guido van Rossum’s resignation message
- The History of NumPy — NumPy documentation
- About pandas — pandas documentation
- PyTorch — about
- Guido van Rossum — Computer History Museum
Comments