why python is terrible

Workspace actions
Then
Then Answer

The Mirage of Modernization: Why Gradual Typing and PEP 703 Fail to Save Python

Can you cure a structural illness by painting over the symptoms? While the Python steering committee attempts to salvage the language's reputation through monumental initiatives like **PEP 703** (making the GIL optional) and **PEP 484** (gradual typing), these interventions do not save the language. Instead, they expose an existential tension: trying to force a dynamic, 1990s scripting language to behave like a modern, high-performance systems language yields a complex hybrid that delivers the benefits of neither. ## PEP 703 and the C-API Fragility Cascade The initiative to make the Global Interpreter Lock (GIL) optional, detailed in [PEP 703](https://peps.python.org/pep-0703/), is heralded as a triumph for multi-core processing. However, this transition threatens to fracture the delicate foundation of Python's scientific ecosystem: its legacy C extensions. For decades, library authors wrote C extensions under the comforting assumption of single-threaded execution guaranteed by the GIL. Removing it requires replacing simple reference counting with biased reference counting or hazard pointers. As core developer Sam Gross notes in the PEP 703 proposal, thread-safety must now be managed explicitly. This shift introduces a critical dilemma. If legacy extensions are run under a "nogil" interpreter, they risk silent data corruption and race conditions unless they are entirely rewritten. This burden falls on the maintainers of foundational libraries like NumPy and SciPy, who must thread-safely refactor decades of low-level C code. ## The Performance Hollow of PEP 484 To combat semantic fragility, Python introduced gradual typing via [PEP 484](https://peps.python.org/pep-0484/). While type hints improve IDE autocomplete and enable static analysis via tools like `mypy`, they introduce a profound architectural paradox: **type annotations are ignored at runtime**. Unlike TypeScript, which compiles away to clean JavaScript, or Rust, where types dictate memory layout and monomorphization, Python's type system is purely cosmetic. At runtime, a `float` is still boxed inside a heavy `PyObject` heap structure. As academic analysis in papers like [Type Systems for Programming Languages](https://www.sciencedirect.com/book/9780120517510/type-systems-for-programming-languages) demonstrates, true static typing yields performance because the compiler can make assumptions about memory offsets. Python’s gradual typing offers none of these optimizations. Developers must write verbose, boilerplate-heavy type signatures without gaining a single nanosecond of execution speed. ## The Hardware Monoculture and Neuromorphic Stagnation Python's dominance in machine learning, sustained by PyTorch and TensorFlow, has created a dangerous software-hardware feedback loop. Modern AI accelerators—including Google’s TPUs and various wafer-scale engines—are optimized specifically to execute the highly structured tensor graphs emitted by Python frameworks. This creates a systemic blind spot. By restricting our computational model to the tensor-manipulation paradigm dictated by Python front-ends, we limit our ability to program novel, non-von Neumann hardware architectures, such as neuromorphic chips. These architectures require event-driven, asynchronous message-passing paradigms that Python's synchronous runtime cannot cleanly express, stalling progress in alternative computing paradigms.
Then
Then
Then Answer

The Myth of the Python Unicorn: Software Chimera and the Alchemical Promise

To demand a high-performance, concurrent, and memory-safe system built natively in Python is to ask for a unicorn—a mythical, harmonious beast that does not exist in nature. The industry’s relentless pursuit of this computational chimera, trying to force a dynamic, interpreted scripting language to perform like a bare-metal systems tool, represents a form of modern software alchemy. We cannot "build a unicorn" because Python's core design principles, famously outlined in Tim Peters' [The Zen of Python (PEP 20)](https://peps.python.org/pep-0020/), directly conflict with the rigorous constraints required for high-integrity, high-performance systems engineering. ## The Alchemical Illusion of Meta-Programming and Magic Methods The drive to construct complex, enterprise-grade architectures in Python often relies on heavy meta-programming, dynamic class generation, and custom import hooks. While this dynamism feels like magic, it introduces severe runtime unpredictability. In *Structure and Interpretation of Computer Programs*, Harold Abelson and Gerald Jay Sussman warn of the dangers of excessive linguistic abstraction: > "Metalinguistic abstraction... is the most powerful tool in the hands of the designer. But it is also a dangerous tool, because it can lead to systems that are incomprehensible and impossible to debug." When we build a "unicorn" framework using Python’s dynamic features, we create a system where code behavior cannot be statically analyzed. Security tools and static compilers fail because variable bindings, class structures, and method resolutions are determined at runtime. ## The Subversion of Python's Elegant Core The tragedy of attempting to force Python into roles it was never designed for is that it ruins the very simplicity that made the language beloved. By trying to turn Python into a high-performance, statically typed, concurrent system, developers create a bloated, unmaintainable hybrid. 1. **Syntactic Overload**: The addition of type variables, generics, and protocol definitions turns Python's clean, readable syntax into a dense, unreadable wall of text. 2. **Asynchronous Fracture**: The introduction of `asyncio` split the Python ecosystem into incompatible synchronous and asynchronous libraries, violating the Zen of Python's principle: *"There should be one-- and preferably only one --obvious way to do it."* 3. **The Dependency Trap**: To make Python run fast, developers must wrap heavy C/C++ or Rust engines behind thin Python interfaces. This creates fragile, complex dependency chains that are prone to compilation and linking failures during deployment. Instead of trying to mutate Python into a systems-level "unicorn," we should respect its true nature as an elegant, highly effective tool for rapid prototyping, scripting, and orchestration. When high-performance, strict concurrency, and memory safety are required, we must use languages designed for those constraints, such as Rust, Go, or Zig, rather than trying to build a mythical beast out of a language designed for simplicity.
Then
Then
Then Answer

Dismantling the Engine: Inside the CPython GIL Source Code

If the pursuit of a concurrent Python unicorn is a form of software alchemy, then the Global Interpreter Lock (GIL) is the physical law that dooms the transmutation. To truly understand why Python cannot escape its single-threaded heritage, we must move beyond abstract design philosophy and look directly at the C source code of CPython, the reference implementation of the language. ## The Physicality of the Lock: `pycore_ceval.h` The GIL is not a conceptual behavior; it is a concrete mutex defined in the CPython internal headers. In the [CPython GitHub repository](https://github.com/python/cpython), specifically within the internal header [pycore_ceval.h](https://github.com/python/cpython/blob/main/Include/internal/pycore_ceval.h), the GIL is represented structurally within the helper runtime state: ```c struct _gil_runtime_state { unsigned long interval; _Py_atomic_address locked; _Py_atomic_int requests; /* ... Mutexes and condition variables ... */ }; ``` This structure reveals that the GIL relies on an interval-based preemption model. As explored in Antoine Pitrou's seminal [design of the new GIL](https://github.com/python/cpython/blob/main/Python/condptr.h) (introduced in Python 3.2), the lock does not continuously poll. Instead, it uses a system-level condition variable to force the holding thread to release the lock after a set duration—by default, 5000 microseconds (5 milliseconds)—if another thread requests it. ## The Illusion of Release: The Evaluation Loop To see the GIL in action, one must examine the heart of the interpreter: the evaluation loop in [ceval.c](https://github.com/python/cpython/blob/main/Python/ceval.c). Here, the interpreter executes compiled bytecode instructions. Within this loop, CPython periodically checks if another thread has requested the GIL: > "The main loop `_PyEval_EvalFrameDefault` check counter is decremented on each instruction. If it reaches zero, the thread suspends execution, releases the GIL, waits for a signal, and reacquires it." This mechanism introduces a profound paradox: **the act of releasing and reacquiring the GIL can consume more CPU cycles than the actual computational work being performed**. This phenomenon, known as the "convoy effect" or "GIL thrashing," occurs when I/O-bound threads repeatedly wake up and force compute-bound threads to yield, resulting in massive context-switching overhead. As David Beazley demonstrated in his classic research on the [Inside the Python GIL](http://www.dabeaz.com/python/GIL.pdf) presentation, under multi-core environments, this battle for the lock leads to severe performance degradation because the OS thread scheduler and the CPython GIL state machine actively fight each other. ## PEP 703 and the "No-GIL" Surgical Extraction The most radical evolution in Python's history is [PEP 703 -- Making the Global Interpreter Lock Optional](https://peps.python.org/pep-0703/), authored by Sam Gross. To understand the sheer complexity of removing the GIL, one must look at what replaces it in the code: 1. **Biased Reference Counting (BRC)**: Instead of a single atomic increment/decrement for garbage collection, memory allocation must now distinguish between the owning thread of an object and foreign threads, tracking local and shared reference counts separately. 2. **Thread-Safe Allocators**: The default memory allocator (`mimalloc`) must be integrated to handle concurrent allocations without a global bottleneck. 3. **Hazard Pointers**: To safely read dynamic dictionary and list structures without locking them, Python must employ lock-free data structures that utilize hazard pointers to defer memory deallocation. This architectural shift proves that removing the GIL is not a simple deletion of a mutex; it requires a complete rewrite of CPython's memory management. It highlights a stark computer science reality: to achieve true multi-threaded concurrency, we must trade the elegant, single-threaded speed of simple pointer manipulations for highly complex, multi-threaded coordination protocols.

Continue this thread

This path ends here for now.

If you want to keep exploring this line of thought, open the editor and add the next question or answer from this endpoint.

Continue this thread in the editor on desktop.

Other paths you could read

Earlier, at The Velvet Cage of Python, the conversation split. If this is not the thread you want, you can switch to one of the other paths below.

Reading key

Highlights

No highlights yet

Select text to save it here.