22 comments

[ 3.3 ms ] story [ 42.8 ms ] thread
> Python 3.14.0 introduced a new incremental garbage collector. But reports of higher memory usage caused the Python team to revert the garbage collector changes in 3.14.5.

If they didn't have very good objective reasons the new GC is better, they never should have shipped it. If they do, they should not have reverted the change.

It's this sort of stuff that leaves me scratching my head why people like Python so much. I hear them say they prefer the syntax and personally I feel like that's such a small part of the holistic experience of working with any particular language. It's one of the reasons why I gave up on C++ years ago for .NET, the whole system of tooling in .NET has never left me feeling like I was pigeonholed into doing things in stupid, self-flagelating ways. Why should I use a language like C++ that doesn't provide a standard set of package management and build tools? Why should I use a language like Python that feels like it's being designed by amateurs?

I felt like the tooling in Racket, CLisp, and Java were similarly pragmatic and not either religiously devoted to some concept of "backwards compatibility" that I seriously doubt most people actually need, or "ease of use" that actually proves itself to be easy when you consider the not-happy-path of the beginner tutorials. Racket, I didn't continue just because the library ecosystem isn't mature enough to keep up with the latest in databases and other 3rd party services. Java I quit largely because of Oracle and some 2010s problems with stagnation. CLisp mostly because it was too hard to socialize. But never because I thought the core language and tooling were holding me back.

When people say they prefer 'the syntax', I think they usually mean a bit more than just that: at least a fair bit of this is semantics. Python provides a lot of features out of the box, and if you work superficially along the happy path, it is very easy to understand how these work and read code using them.

Take list comprehensions for example. It is mostly syntax: you can do virtually all list comprehensions with just a map and filter function. But the way that it is integrated and presents the code, makes is vastly easier to follow for most developers, which tilts the balancer in favor of doing away with loops and mutable state. Is it syntax that made them do so? Yes, maybe. But its the actual semantics that provide the value.

It's better in some ways (order-of-magnitude reductions in pause times were cited) but worse in other ways (higher peak memory usage). That the higher peak memory usage was catastrophic for some users only became apparent through post-release feedback.
Really? You've never reverted a positive change because it contained a regression only discovered after release?
I suspect 3.14.4 could have been tweaked slightly to address the issue without a revert - they could have prioritized checking the liveliness of objects sorted by size. I’m pretty sure that would fix the max RSS issue without needing a revert and the people unhappy with 3.14 could keep using 3.13 or switch to 3.14 and simply inject explicit calls to gc.gc().

Figuring out how to measure the size of an object can be tricky of course, but I suspect there’s all sorts of things you could try including figuring out how much memory got deallocated after you gc a cycle and attributing it to where the object got allocated as a heuristic to measure the mean allocation size.

We definitely noticed behavioral differences in 3.14 regarding gc which could show up in particular test suites we have that are purposely ensuring all objects of a certain type were collected after a gc.collect() run. Between this and other issues (changes to the runtime API for typing, the first decently runnable version of free-threading, kind of a longer time for some C-based dependencies to catch up), the transition for my projects (SQLAlchemy) to 3.14 was generally more bumpy than that of say 3.12 or 3.13. will be interesting to see if 3.14.5 allows us to relax some changes we had to make to the test suite.
The usual GC tradeoff is between memory and CPU performance. If you set the memory max high, the GC will run less often, you get less pause time.

So I do not understand why it's a surprise that minimizing the pause time requires more memory. Is it because there is no knob to set either the max pause time or the max memory ?

Do people use python for new projects apart from ML stuff which hasn't moved to all-native yet?

My experience with Python is a really bad one for professional work: it's chaotic and slow, and has by far the worst versioning and packaging story of any mainstream language, yet its proponents keep praising it in denial.

I guess Python is an ok target for agentic coding, but my god do look Claude's commit messages pretentious, with code bases quickly heading into absolute unmaintainability. At least it had found gross JS injection vectors in a Django app that really shouldn't have made it through a code review, architecture level as they were, but oh well. A mature Django app is also not a nice dev experience IMO, with tons of implicit behavior all over the place encoded in a mix of magic filenames, database naming conventions, and URL routing quickly descending into regexp hacks.

I find the cult of python is hard to shift from some people. They just can't see it.
Yeah I still use Python. I've been using it for a long time, I can get stuff done quickly with it, but I feel the same way you do to a big extent. Not sure what to use instead. I hate anything having to do with Javascript even though some parts of the JS world beat Python hollow (it's the other parts that are even worse than Python). Golang? Rust? Both too low level. I do use C++ (also low level) when I need something to run fast, but it's not the first stop. Erlang/Elixir? I like Erlang (haven't used Elixir) but it's too small a world and I'd want something with a serious type system if I'm gonna change languages. Haskell? Too much headache to do even simple things, such as logging. Scala? The bureaucracy of Java with the headaches of Haskell. OCaml? Maybe underrated and I should look into it more, but again it seems like Haskell's poorer cousin. I'm sure I'm overlooking some good ones though.
I left the Python ecosystem some time ago.

Reasons: The Python 2->3 transition, asyncio package, async/await function coloring, abysmal package management, the GIL and poor performance, breakage from version to version. I'm ambivalent on type hints. I regret nothing, especially after seeing how the GC and JIT projects have been handled.

Golang addresses all of my problems with Python. Native code, good performance, an exceptional toolchain, a built-in package solution, great concurrency support, and they prioritize compatibility across versions. AI is good at writing Golang (as good as any other language I've tried), and AI benefits a lot from static types.

Instead of "Reference counting primer" I would like to see a primer on how exactly such a huge change can go into Python without formal PIP process.
but I aways use Python to analysis my medical data.
I see in the front page Pyodide 314.0 and now also Python 3.14. Is it a coincidence or is there some meaning to these "pi versions" in Python?
In Pyodide's new versioning scheme, the major version is the concatenated major and minor versions of the corresponding Python release. Pyodide 314 is based on Python 3.14 (which is just the fifteenth minor version of Python 3, counting from zero, and just happens to match the common approximation of pi).