My concern, and IMO what should be the overwhelming concern of the maintainers, is not the code that is being written, or the code that will be written, but all the code that has been written, and will never be touched again. A break like this will force lots of python users to avoid upgrading to 3.17, jettison packages they may want to keep using, or deal with the hassle of patching unmaintained dependencies on their own.
For those Python users for whom writing python is the core of their work that might be fine. For all the other users for whom python is an foreign, incidental, but indispensable part of their work (scientists, analysts, ...) the choice is untenable. While python can and should strive to be a more 'serious', 'professional' language, it _must_ have respect and empathy for the latter camp. Elevating something that should be a linter rule to a language change ain't that.
Exactly this. I totally agree. It's incredible to think that some people still run python 2 scripts; something unpalatable to the point of being nauseating for a day-to-day python programmer, but totally understandable in the context of incidental usage by a scientist dealing with legacy systems.
If these things start happening to python 3 on a larger scale, might as well throw in the towel and go for python 4.
Programming languages are too obsessed with unicode in my opinion:
String operations should have bytes and utf-{8,16} versions. The string value would have is_valid_utf_{8,16} flags and operations should unset them if they end up breaking the format (eg str[i] = 0xff would always mark the string as not unicode, str[i] = 0x00 would check if the flag was set and it so check whether the assignment broke a codepoint and unset the flag if so)
There are zero reasons to not go full UTF-8, everywhere, all the time. Strings should not be allowed to be built directly from bytes, but only from converting them into explicitly UTF-8 (or, for API specific needs, other encodings should you want to enter the fun world of UCS-2 for some reason), which can and should be a cheap wrapper to minimize costs.
Bytes are not strings. Bytes can represent strings, numbers, pointers, absolutely anything.
That would only work if you never deal with legacy (pre-utf8 data), or if you are American and all your legacy data in ASCII.
If you are actually dealing with legacy data, you want your programs to not care about encodings at all.
File names are sequence of bytes... if it's not a valid UTF-8, why would program even care? A C program from 1980 can print "Cannot open file x<ff><ff>.txt" without known what encoding it, why can't Python do this today without lots of hoops? Sure, the terminal might not show this, but user will likely redirect errors to file anyway and use the right tools to view it.
File contents are sequence of bytes. Every national encoding keeps 0-31 range as control characters, and most of them (except systems like Shift-JIS) keep lower half as well. Which means a program should be able to parse .ini file which says "name=<ff><ff>", store <ff><ff> into a string variable, and later print "Parsing section <ff><ff>" to stdout - all without ever knowning which encoding this is.
There are very few operations which _really_ care about encoding (text rendering, case-insensitive matching, column alignment) and they need careful handling and full-blown unicode libraries anyway, with megabytes of data tables. Forcing all other strings to be UTF-8 just for the sake of it is counterproductive and makes national language support much harder.
I agree that there need to be utf-8 specific functionality but not everything `is` utf-8, for example filenames and filepaths. For example a JSON document should be utf8 encoded, but json strings should be able to encode arbitrary bytes as "\x00...\xff". since they can already contain garbage utf16 we would not lose much.
Not sure 3 did anything to the number of circular permutations of encode and decode one need to fiddle with until that damn csv is pulled in correctly.
The Python maintainers have switched from considering backward compatibility useful but costly to considering it actively harmful; there was a campaign a few years ago to convince library maintainers to stop making their code Python-2-compatible:
> The Python 3 statement was drawn up around 2016. Projects pledged to require Python 3 by 2020, giving other projects confidence that they could plan a similar transition, and allowing downstream users to figure out their options without a nasty surprise. We didn’t force people to move to Python 3, but if they wanted to stick with Python 2, they would stop getting new versions of our projects.
I know this sounds like a joke, and you probably think you're misreading, but no. Projects pledged to require Python 3 by 2020. They made a promise to break backward compatibility. Not just a few minor projects, either; TensorFlow, Spark, IPython, Pandas, NumPy, SymPy, Hypothesis, etc.
Since that happened, everyone who considers backward-compatibility good (if costly), rather than evil, has abandoned Python.
The "other users for whom python is an foreign, incidental, but indispensable part of their work (scientists, analysts, ...)" would have to fork Python, but it's probably too late for that; they can hardly hope to fork TensorFlow, Pandas, etc., as well.
1. That python 3 statement was not drawn up by "the Python maintainers". It was drawn up by downstream library owners.
2. To the extent you object to changes in the core language, the python maintainers do have a backwards compatibility statement and prominent timelines for deprecation. You may disagree with these, but they are public.
3. At the time it was written, the python 3 statement proposed dropping support for a version of python with known security problems and no plans for security updates. It seems like your argument is with the python 2 to python 3 transition, which feels like a conversation we've had here before.
Anyone can of course apply security fixes to Python 2, because it's open-source.
My objection is not to library owners dropping support for Python 2, which is a perfectly reasonable choice for them to make—backward compatibility can be costly, after all, and the benefits may not be worth it. My objection is to library owners pledging to drop support for Python 2, because that entails that they think backward compatibility is itself harmful. To me, that's pants-on-head crazy thinking, like not wanting to wear last season's sweater, or not wanting to use JSON because it's too old.
Observably, since this happened, the Python maintainers have been very active at breaking backward compatibility. (And there's substantial overlap between Python maintainers and major Python library maintainers, which I suspect explains the motivation.) I think this is probably due to people who don't think backward compatibility is actually evil (the aforementioned "all the other users for whom python is an foreign, incidental, but indispensable part of their work") fleeing Python for ecosystems like Node, Golang, and Rust. This eliminates the constituency for maintaining backward compatibility.
I do think the botched 2→3 transition was probably the wellspring of this dysfunction, but I don't think that in itself it was necessarily a bad idea, just executed badly.
As a result of this mess, it's usually easy for me to run Lisp code from 40 years ago, C code from 30 years ago, or Perl or JS code from 20 years ago, but so difficult to run most Python code from 5 years ago as to be impractical.
It seems to be a gross exaggeration to say most Python code from 5 years ago doesn't work on current Python versions. Python 3 was mainstream a decade ago, and almost all code written for Python 3.3 or 3.4 still works on Python 3.13. Maybe some libraries have had breaking changes, but at least for common libraries like Numpy, Scipy, and Matplotlib, most code from a decade ago still works fine.
There's plenty of Python 2 code from 5 years ago, and virtually none of it works on current Python versions. A decade ago virtually all Python code was Python 2 code; in 02014 Python 3 was almost unusable. Perhaps what you mean is that most individual lines of Python code using Numpy and Scipy from ten years ago work fine in current Python versions, but very few complete programs or even library modules do.
They made a new version which is highly indicative of breaking changes if not the entire meaning behind bumping the version. What's the problem? I think it's bold of you to rag on volunteers for a supposed botched upgrade, whatever, but I don't know anyone writing python 2 today?
Plenty of people are writing Python 3 today, too, but because things like this proposal seem reasonable, and things like removing cgitb and asyncore are actually happening, most of them will regret it eventually. Sometimes volunteers screw up, and sometimes they have dysfunctional social dynamics that hurt people.
I agree, it's too late for Python. But it's not too late for people who think the work they're doing has serious intellectual content of lasting value to choose a different language today, so that their work doesn't become unusable five years from now, and it's not too late to keep the same thing from happening to other programming-language ecosystems.
The people who you describe as valueing backwards compatibility are exclusively downstream consumers of others work. They value infinite free labour by others to any maintenance by themselves. This is of course a perfectly rational but unsupportable position.
The great thing about free software is that they have the legal right to do that maintenance, and it's actually not very difficult to do. The Python 3 Statement was an attempt to use public shaming to disincentivize them from doing it. Astoundingly, it seems to have been successful.
Regarding open source that labor is expensive, rare, and at any given time insufficient to need. In this case it also has a value only in context of the majority containing to do it. What is being signalled here is a lack of desire to keep doing it followed by nobody opting to volunteer for the mission.
Basically you've been a guest long enough and now the towels, entertainment, and snacks are going away. If this appears blunt it is pretty obvious why bluntness is required anything else asks for a decade of free support given by inched at the expense of more laudible goals.
No, what is being signalled here is the intent to publicly shame anyone who continued to maintain backward compatibility. Given that public humiliation is among most people's greatest fears, in retrospect, it shouldn't be surprising that anyone volunteered for the mission. But it did surprise me.
Your comment would be correct if we were discussing a lack of continued support for Python 2, or even a public announcement of a planned cessation of such support. But we're discussing a public promise to break support for Python 2, in the form of a petition seeking more signatories. Although the difference may be too subtle for you to have noticed, it's an entirely different animal. It's like the difference between hotels that don't promise you a room that allows smoking, and hotels that promise you a room that doesn't allow smoking. The second case is a promise to keep your room free of the nauseating stench of Python 2.
But what user would want that? Why would you prefer a language or a library that promises to break backward compatibility? What's the benefit to you of the language making your code cease to function every year or two? Job security, perhaps?
They would want that because the cost of constantly saying no to users with attitudes that range from grateful to entitled is non-zero. Support requests for 16 year old software should come with a support contract and a check.
I suspect anyone willing to pay enough could get support for whatever they please and with enthusiasm.
No, it's obvious why Python maintainers would want to drop backward compatibility. What I don't understand is why users would want it. I thought that was pretty clear in my comment; I'm not sure how you managed to misinterpret it to be saying I didn't understand something that's obvious.
At this point you have seriously transgressed the boundaries of civility; having been informed that you had completely misinterpreted my previous comment, the least you could do is to apologize. Instead you are responding with sarcastic remarks apparently predicated on the same misinterpretation you've just been corrected on. You've exhausted the presumption of good faith. Probably even your initial comment was merely trolling; the exaggerated wording full of absolutes and stereotypes should have clued me in.
I know you really, really want me to think that I was owed new Python 2 versions of other people's libraries, but I'm not going to. I've already explained several times that what I think is bad is not people dropping backward compatibility with Python 2, but demonstrating the intent to publicly shame anyone who continued to maintain backward compatibility.
All of your comments are arguing against a position I've never held as if it were my position, well after you have no reasonable excuse for that error. That's dishonest and offensive.
Sure I guess? But we've just barely stopped talking about Python3, and that was released 13-16 years ago[1]. Is _this_ change worth another decade of thrashing the ecosystem? Is __any__ change?
[1]: depending on if we count 3.0 vs 3.2 when it was actually kinda usable.
Again, python does not use semantic versioning. 3.12 and 3.13 are different major versions. The deprecation policy is documented and public. https://peps.python.org/pep-0387/.
In the doc you linked, they reference "major" and "minor" versions. So they claim to have some concept of version numbers having different significance... Why don't they adhere to semantic versioning if they dress their version numbers like that?
At least Linux just admits their X.Y scheme means nothing.
Python should have the ability to set per-module flags for these kinds of incompatibilities.
I guess that __future__ is doing something similar, but I am thinking of something like declaring how options should be set for your module and then only the code in that module is affected. (It would be nice being able to set constraints on what options your dependencies can enable)
I guess that for std functionality this is impossible (like if the dict changed its key sorting it might be too hard to dispatch on the original file) but for syntax it should be perfectly possible.
Disagree, I’m so disappointed in companies who do sprint type development refusing to use Python. It works well with the “Silicon Valley startup ecosystem”.
That being said, as far as workplace differences I’d say Java shops would be the ideal, slower, less long term problems but so much more initial investment.
As SV startup with Python monolith, yes, it's very common for startup but generally gets ejected because lack of strict typing and speed. We are replacing with Go, Node and .Net.
Tell that to the people who downvote me which seems unprofessional as hell.
If I want to learn .Net which is more time consuming and more difficult to find employees why would I use it? Makes sense if you are in an area with a lot of windows people, but that’s not the case anywhere other than Texas.
And the compiler enforce typing. Admittedly not as nice as Go since you have to rely on external tools but workable.
People like their curly brackets though. Just not as helpful when dealing with system problems.
.Net came from group we acquired who yes, deployed things on Windows. However, their code now runs on .Net Core, in Linux Containers on Kubernetes. It's very performant as well, my only gripe is startup JIT. .Net does great in startup culture if you are not chasing trends and want code that works.
Hmm, usually the application start latency is very good. Significant improvements have been made to ensure that Tier-0 compiles fast. A base ASP.NET Core template takes about 120ms to start on my machine as tested with .NET 8 and Hyperfine (I modified it with starting the server with await app.RunAsync, then raising a CancellationToken on it in 10ms which outputs an error message in console about the fact and exits).
There is a good chance something else might be going on in one of the dependencies or perhaps some other infra package a team maintains, that slows this down. Sometimes teams publish SDK images on accident that have to be pulled over the network if they got evicted from the node cache, or try to use self-contained instead of runtime image + plain application - I know at least two cases where this was causing worse than desired deployment speed on GKE (arguably GKE is as much at fault here, but that's another topic).
It's very likely it's some library but at this point, I'm over caring. It's 20 seconds, everyone can cope with deployment rollout in Kubernetes taking 3 minutes.
If I’m understanding this correctly this proposal would fully break compatibility with many (most?) codebases, actually remove syntactic sugar and force more characters for the same functionality. I fail to see how this is even being considered.
I don’t understand the idiomatic viewpoint either here, I understand the author personally finds it confusing when excepts aren’t verbose but I think you would be hard pressed to find many python developers who agree. Even outside the ecosystem, most languages have been adding more support for bare excepts (like js with bare catch) so this feels like a step backwards.
But maybe I’m just not understanding this proposal!
>> I fail to see how this is even being considered.
To me it stinks of an ego-centric person thinking they're a "language developer" and knowing better than the actual users of the language what's best for them. Just because something can be misused doesn't mean you have to take it away.
I haven't noticed, but since Rust came along is there a trend among languages to enforce "safer" programming at the language level? I could see that kind of thinking getting way out of hand. If that's the case, I would see this one as "I'm going to save the world with this dumb little change that breaks things for a bunch of people!"
I would hope a PEP like this came about from frequent user requests but that doesn't seem to be the case.
It's interesting that you mention Rust, since Rust takes backward compatibility quite seriously; that's why it's still on version 1.x. Granted, sometimes there is a compiler bug that causes some old code not to compile with newer versions of Rust, but that is rare and never intentional like this PEP.
(Just to be sorta pedantic) I don't think it's the version 1.x that promotes backward compatibility, but editions.
In 2016, you could call your function `fn async(...) { ... }` without any issues, and you can still compile this code with the modern version of rustc. If you want to use the async features of Rust, you need to change your edition to at least 2018, which brings breaking changes in the language.
And you can have a project that mixes those editions, and cargo will correctly use the edition asked for each crate.
(So, I guess the ideal world would to first look at the package management in Python, and *then* try to introduce breaking changes. And I'm withholding how much I'm angry at the PSF for postponing having a decent package manager for 20 years and investing in removing the GIL and adding JIT.)
Not meaning to apologize for Python here, but you have significantly more ability to segregate “editions” when you statically compile code.
All the more reason to take a breakage very seriously. This is even worse than the walrus operator. At least I can ignore that. This breaks working code for some notion of purity.
Code compilation doesn't really have much to do with it. Python already has a somewhat similar ability - opting into certain language features of python on a file-by-file basis - using __future__[0]. It'd be pretty easy to add something like Rust editions by looking for a special statement in the file. And to make it more convenient, put it in the __init__.py and have it be transitive to the module.
>> It's interesting that you mention Rust, since Rust takes backward compatibility quite seriously;
I mentioned Rust because the memory safety guarantees are a significant new thing for a language like that. I forgot about "managed" languages like C# because that's quite far from my mind, but that's another significant attempt at safety. This kind of little detail in the PIP is really insignificant by comparison, so I was speculating that it might be driven by some kind of "save everyone" mentality. If so, wondering if that's a trend lately and I hadn't noticed.
They do take it seriously, I agree. However, the commonly repeated meme that Rust only makes backwards-incompatible changes by mistake or to fix soundness issues is wrong. They allow themselves to make changes if they're judged to have a low (but nonzero) risk of causing backwards incompatibility in the wild. For example, adding a new function to a standard trait can be backwards incompatible but they do it all the time.
Indeed. Although it's worth noting that the same is true of e.g. stable enterprise favorites like Java, which regularly makes minor breaking changes that are judged to have little impact (which is why every Java release is accompanied by a compatibility guide; see the "Important Changes", "Removed Features", and "Other Notes" sections of the most recent release notes: https://www.oracle.com/java/technologies/javase/23-relnote-i...).
No. A serious language designer will try to make things they like at the beginning, not trying to patch it later. Patching via breaking public API (language design) is never a good thing.
There have been people trying to enforce safer programming at the language level at least since Java positioned itself as the safer alternative to C++ way back in the 90s.
I'm pretty sure Bertrand Meyer's OOSC[0] from 1988 had something like "if a language has a feature which comes with warnings that you shouldn't use it, it shouldn't have that feature" (paraphrasing).
Rust had extremely successful marketing based on its security claims. It's no surprise that other languages jump on that bandwagon to not get left behind, is it?
PEP contains lots of best practices that aren't enforced by the interpreter though, doesn't it? e.g. PEP 8. It's been a while so maybe PEP 8 is more unique than I realize? It seems like a pretty sensible recommendation that wouldn't necessarily need to change the way exceptions are handled by Python. Right there in PEP 8 it says in big text "A Foolish Consistency is the Hobgoblin of Little Minds." I imagine that enforcing this in the interpreter would fall under that, but it seems like a good piece of advice for folks new to the language, or more likely, new to coding.
I don't know, but it seems like, as a language grows, the type of people working on improvements changes. In the beginning, the contributions come from people trying to solve application problems. In the end, the committees and contributors seem to be less connected to reality and more internal and isolated. They make changes that seem conceptually sound but aren't grounded in what the users of the language actually care about.
Python feels like they are fixing the language the best they can by slowly and properly adding explicit types. Character count is not a valid argument when we have hard drives that are multiple terabytes and IDEs and LLMs that will gladly auto complete a full word with the import. Foo, bar variable names and i, n incrementers should be banished to freshman level undergrad tests meant to intentionally confuse the student.
I'm hoping Python 4 will be a big breaking change similar to the previous one and full support for explicit types will be one of the reasons.
Character count is a valid argument because a human is going to have to read the code at some point. Otherwise, Java style names like "countriesFromAsiaWhereAreTheMostPlanesAndBoats" would be just fine.
Edit: I don't get the hate for "i" or "j" as increment variables. When you're working with numerical data they closely match how you would express the same operation with mathematical notation and are idiomatic to the point that everyone with non-trivial experience in programming knows what they represent. There are better options in some cases (e.g. "for name in names:"), but there's nothing inherently wrong with i, j, k, etc.
At my last job, there was a frontend developer who added a linter rule that variable names must be at least 2 characters long. The project already had 20,000 lines of code. Every time anyone made a change to a file, they would have to rename all the one letter variables. Usually, this meant all the for loops in the file. I tried to explain how pointless this rule was, but he wasn't having any of it.
Most people just renamed variables like i > ii, which was worse.
a good linter rule would have exceptions for loop variables by context by just by name like i, j, k. Often just by name is good enough at least for a solo dev or small team. I require them to be at least 3 chars EXCEPT those.
What is even the point of digging in deeper, did they not see everyone around them doing the i>ii workaround? You've lost, call it a failed experiment and move on.
It's not in bad faith to say that there's a cognitive load to having longer line lengths or, god forbid, wrapped lines when you're trying to figure out what the code is actually doing with that data the variables represent. Obviously the Python "except" case being discussed is not a big deal in this regard, but you made a blanket statement about character count being no big deal because of big hard drives and IDEs.
I work on code that uses `pid` pervasively, and it would drive me crazy if someone insisted I write out `processId` every time. Same with `tx` or `rx` for channel endpoints, `i` for loop variables, `txId` or `tid` for transaction ID in databases, etc. If something is very common in the domain you're working in it's more annoying _not_ to abbreviate it.
Recently, I saw somebody writing that in their projects, they define a Glossary.txt of terms, and then set up their tooling to flag any use of an identifier that isn't a word or defined in the Glossary. (Allowing also for compound words like camelCase and snake_case, of course). So in their project they could define `pid: process identifier [...]`, and then their tooling would be allow a variable named `new_pid`.
Upsides: consistent vocabulary in your project, a centralized place to look up jargon, and a subtle friction to avoid adding new terms that people might not immediately grok.
I wish I could remember the source; because it sounded like a nice setup to steal.
By the time you've written a Python that has all explicit types in it, and harvested the speed advantages the interpreter can have when it can count on that, and all the other cascading changes that would result, and the fact that you would discard 100% of all previous modules both Python and C, you might as well just start using one of the existing statically-typed languages that already has a mature ecosystem.
Python should be working on being the best Python it can be, not being an adequate Python and an inadequate bodged-on static language on the side.
Python 3 does not, but the hypothetical Python 4 would be crazy to put types on everything and then not accelerate its interpreter with the resulting data.
The problem Python 3 has is dynamic types, as the dynamically-typed languages implement them, are viral; one little crack lets them in somehow and all the code operating on the data has to assume it's viral.
I would be pleasantly surprised if they manage to do that. The current type checkers of Python, as useful as they are, hit the limit pretty fast, because of its extremely dynamic nature and the type system struggling to type check valid expressions. The number of times I've seen common pandas and numpy code not type check
CPython doesn't, but there is Mypyc [1] which compiles statically typed Python to faster C extensions leveraging the type information. As usual, this comes with tons of limitations [2]
It's basically documentation to make it easier to use functions. After that people decided to enforce it basically as lints and only where possible. It feels like Python to me.
Javascript somehow manages to grow without breaking backwards compatibility. So does C++. Breaking countless packages (and forks of packages) in the pursuit of something as nebulous as language purity is a big mistake. I happen to like explicit typing but it's not the kind of thing you can graft onto a mature language without making awful compromises. Also, it pushes massive externalities onto the millions of people who rely on Python for their work.
I'm hoping "Python 4" will be another language entirely that displaces it, Fixing some ecosystem problems upfront (packaging, concurrency/GIL). Nim is possible candidate, though Go is pretty popular w/ Pythonistas.
My personal unhappiness with how Py3K was handled, plus recent PSF events make me feel new leadership would also be a boon..
FWIW I've been semi-actively designing a language of that sort, which I call Fawlty, since about February of this year. I was planning to start writing about it in July but then all the PSF drama stuff happened and I didn't want people to assume incorrectly that the idea was motivated by my disappointment with the community.
In fact, I'd been thinking about doing it since probably November of the previous year - and it's motivated by long-held beliefs that
Python's design falls short of the Zen;
several of the most common beginner pitfalls could and should be avoided by syntax changes and by a different approach to parsing the code; and
the standard library is full of ancient APIs that look terrible because they're motivated by C and Java designs that used to be the best we had but seem highly unidiomatic now.
It was somewhere in November or December last year that I first wrote those thoughts down more concretely (with details about what those pitfalls are etc.).
Given that pace of progress, however, I've more or less given up on ever expecting myself to publish something usable - by myself, at least. I've decided for now that it will be more practical to add blog posts about my ideas to the queue, and possibly see about my own implementation later.
I don't think so; if you recognize "Fawlty" as the name of a tower then you equally well recognize Fawlty Towers as a spiritual successor to Monty Python. The goal isn't to "escape" but to suggest that this is a new effort with the same background principles.
I have found Nim more useful/usable than Python + Cython since about 2014 for my personal use cases and almost always much more efficient on a "per unit of effort basis" than Go. At least if you are willing to write whatever code you need or link in C/C++ instead of relying on people gifting it to you.
I think the ecosystem leadership position Python finds itself in lately may well make Py-core silly enough to think "our users will tolerate our breaking-all-their-code antics no matter what". I suggest a more consenting-adults alternative here: https://news.ycombinator.com/item?id=41790766
> Python3 did not break enough to justify the jump from 2 to 3
I agree - it should have broken more (and thereby become able to fix more). It should also have been usable out of the gate and not majorly reworked (there was a serious battle over the syntax of byte and string literals, and possibly some other things, that resulted in 3.0 and 3.1 not seeing a full 5-year maintenance lifetime), and of course developers should have actually fixed stuff promptly and accepted that the obviously superior new ways of doing things were, in fact, obviously superior.
Unfortunately, a lot of other developers don't seem to agree.
And as for explicit types - I really wish people would stop trying to fight the type system - of Python, and of whatever other language. Python is not meant to support static types. It's not designed to reject your code at compile time for a type error and it isn't designed to take type information into account when generating bytecode. It's designed, instead, very explicitly, to let you care about what a given object can do, rather than about how it categorizes itself.
Python 3 broke almost literally every non-trivial Python file on earth, and was saved only by `2to3` and `six` being able to automate or library-ify away 75% or so of the changes. The remaining 25% was make-work for teams needing to avoid the deprecation/EOL/vuln demons (or wanting to take up new language features that became Py3-only), and many teams took the opportunity to instead spend that time rewriting their codebases in Go or TypeScript+Node.
I don't know how much more breakage you really wanted out of Python 3 if "permanently scoured the public image of the language and caused many Python shops to, at least partially, stop being Python shops" wasn't enough.
(I say this as a still-fan of Python who has written quite a lot of it and contributed to MicroPython/CircuitPython's internals - I just also worked at a Python shop during the Py2->3 hell, and frankly, even my current dayjob still talks about that transition as a nightmare to watch out for if any other language starts doing similar talk.)
My experience was different. The single biggest change I had to deal with in a zillion places was change of str from bytes to unicode. That wasn't just a syntax change. It forced me to chase down all the places where I'd been handling treating strings like bytes because it worked 99% of the time as long as I was only dealing with ASCII. No amount of clever AST re-writing would save me from those earlier mistakes.
Python 3 made entire classes of errors impossible:
* Beginners don't introduce ACE exploits into their program from the get-go, because `input` is what `raw_input` was before and the "convenient" wrapper using `eval` was no longer available - so instructors were forced to teach students about explicit type conversions, like they should have been doing the whole time.
* You can't get `UnicodeDecodeError` from calling `.encode`, or `UnicodeEncodeError` from calling `.decode`, because you're never in the position of pretending that a sequence of bytes is a "string". There's no illogical "basestring" type and no `str` objects with ambiguous semantics - `.decode` and `.encode` do what they say, and belong to separate types. These problems resulted in a huge mess of duplicate Q&A on Stack Overflow full of incorrect advice from people with no clue what they were doing, and empowered people to ignore essential truths about Unicode until it blew up into a bigger problem.
* Similarly, when you read from a text file, you actually get text now.
* `print` used to be confusing and have weird ambiguities and tons of special syntax. Being a function means it can be taught the same way as any other function call; plus you get use as a higher-order function, unpacking operators (you can't do `print(*range(10))` in 2.x).
* `1000000000 in range(1000000000)` no longer hangs. `xrange` does not solve this problem (although it does improve matters quite a bit by avoiding high memory usage).
* Sorting heterogeneous lists correctly produces an error, rather than an ordering so strange as to merit its own Stack Overflow Q&A (https://stackoverflow.com/questions/3270680 and many duplicates). (You can still replicate the old order if you want.)
* `isinstance(x, int)` doesn't fail because of `x` being too large. This was a bizarre speed bump in such a high-level language.
* You can write '£' in your source code and you only have to declare an encoding if you use something other than UTF-8 (which Python correctly identified as the eventual winner).
When Python 3.2 came out I found it to be a breath of fresh air. By 3.4 I was already starting to wonder why so many others were dragging their feet on migrating.
> force more characters for the same functionality
This is actually a good thing in some cases (possibly this one). Risky stuff should inherently be harder to do than safer stuff, otherwise people will reach for the risky alternatives when they don't need to, just to save time - or because they don't realize the risk.
Or at least, that's often the case. What's lacking here is evidence that this is actually happening. I can believe it, but evidence is necessary for breaking the language.
>actually remove syntactic sugar and force more characters for the same functionality. I fail to see how this is even being considered.
Python is not APL. Getting at the functionality in fewer characters is not a design goal - it's just a usually consequence of the actual design goal.
This is being considered because "Explicit is better than implicit." and because it helps avoid a common class of error (e.g. `except: continue` prevents aborting a loop with Ctrl-C, which is often not intentional).
Or as the PEP puts it:
> While this syntax can be convenient for a “catch all” handler, it often leads to poor coding practices:
>
> 1. It can mask important errors that should be propagated.
> 2. It makes debugging more difficult by catching and potentially hiding unexpected exceptions.
> 3. It goes against the Python principle of explicit over implicit.
Python isn't any other language, either. It certainly isn't taking design guidance from JavaScript (which runs in an environment where the page is expected to show something coherent, and not a loud screaming error, no matter how absurd the input data and/or code).
As for how much code it would break, you made me curious:
But drilling down further, over 2/3 of those bare excepts are in local copies of Python itself (i.e., multiple versions of the standard library) that I built from source. Probably all the rest are in dependencies. I don't write code like that myself if I'm even remotely paying attention. (Of course, that only tells me how many occurrences there were, not how many files have them. But the first two results imply an average of about 2 `except`s per file, so.)
Python is compiled to bytecode just like Java and C# - it just also happens to provide an environment out of box that will do it on the fly, and makes it much easier to access the "compiler services" in the standard library (and the built-in `eval`). And it's always been that way. The idea that some languages are "serious" and others are not is already suspect. The idea that being a "serious" language requires being "compiled", or that other languages are just for "scripting", will severely limit you as a developer.
There are two "problems" this PEP is trying to solve.
One is that bare excepts are permitted. The argument against this is that explicit is better than implicit. A matter of taste, but I don't find this convincing.
The other problem is what bare excepts mean. Bare excepts are syntactic sugar for `except BaseException`. This means that an application containing a bare `except` followed by the vast majority of real-world error handling will continue to run even if SystemExit or KeyboardInterrupt is raised. This is almost always a bug.
I do find this second argument convincing, and I wish Python did not contain this design wart.
If I could go back in time and change Python syntax, it would make it hard for people to silently treat these special interrupts as "handleable" like regular errors. The tiny set of applications that really can and should handle them (e.g. TUIs or the mailman example discussed in the final section of the PEP) can explicitly do so with e.g. `except KeyboardInterrurpt` or even `except BaseException`.
But I agree with the consensus here that this does not rise to the level of something being worth a backwards-incompatible change.
Just noting it here: your code is incorrect. In case of a KeyboardInterrupt error and another error raised by `log_tons_of_debug_info()` (there's no error free code, right?), KeyboardInterrupt would end up being masked (it would go into the __context__ attribute of another error). The program won't abort its execution. And it's just one example out of many where it's critical to not mask error types.
Correct code would be:
try:
something()
except BaseException as ex:
try:
log_tons_of_debug_info()
finally:
raise ex
But really, you don't want to mess with BaseExceptions at all, so just do `except Exception` instead of a bare `except:`.
Why wouldn't I want to mess with BaseExceptions? They are not magic, and add only 3 classes to the list:
SystemExit - You _definitely_ want to catch this one for logging. If a library (not top-level app) calls `sys.exit` you at least want to know what's happening, if anything so you can talk to author and get them to use proper exception types.
KeyboardInterrupt - I normally want to catch this one as well. If the program was taking too long and I hit Ctrl-C to stop it, I _do_ want to see all the debug output. And if I don't, for some reason, there is always Ctrl-\ which kills python immediately and unconditionally.
GeneratorExit - this one is tricky and I agree that in a lot of cases, you don't want to print logs on it. But it also very rare - it only appears in async functions (emitted by yield), and never propagated to caller. So as long as you are not doing async, you can simply ignore it, which covers majority of the the code I write.
Because accidentally masking some BaseExceptions like `asyncio.CancelledError` can lead to things like memory/resource leaks and potentially your production app going down in pretty hard to debug ways.
> Just noting it here: your code is incorrect. In case of a KeyboardInterrupt error and another error raised by `log_tons_of_debug_info()` (there's no error free code, right?), KeyboardInterrupt would end up being masked (it would go into the __context__ attribute of another error).
Your snippet has the opposite problem: if ex is a regular Exception, but then KeyboardInterrupt is raised by `log_tons_of_debug_info()`, then your snippet would mask that by re-raising ex in its place. I think the original snippet is probably better on balance, even ignoring difference in code complexity.
Anyone reading your code is going to assume this is a bug. The PEP is right that explicit is better than implicit. You should write `except BaseException` (whether or not this PEP is approved).
"except:" is explicit enough and "except BaseException" is redundant.
Moreover I think there is a real risk people are going to write "except Exception:" instead, which breaks in the fringe case an exception that derives from BaseException (enforced by interpreter) but not from Exception is thrown.
Even if catch Exception is what users usually mean, changing code from "catch (BaseException):" to "catch Exception:" may break some code if improperly reviewed.
It's also not worth breaking production code over this.
> "except:" is explicit enough and "except BaseException" is redundant.
Take that up with the consensus view of the python community, as reflected by python linters in their default configuration, almost all of which warn on bare except.
The debate in the PEP is whether this should be a syntax error. The debate about whether it is good style is over though.
> It's also not worth breaking production code over this.
> Take that up with the consensus view of the python community, as reflected by python linters in their default configuration, almost all of which warn on bare except.
I don't fully agree on it being a purely a style issue (though the warnings from linters are wholly justified). From what I understand, "except:" is a code smell because you don't actually want to catch BaseException instead of just Exception.
Linters wouldn't have warned about it if it meant "except Exception:". The real issue, IMO, is the fact non-exceptions (Ctrl-C, generator exit, program exit code, etc.) have been crammed into the exception system.
>which breaks in the fringe case an exception that derives from BaseException (enforced by interpreter) but not from Exception is thrown.
For many users, in many cases, this would be fixing the code rather than breaking it.
Forcing people to write either `except BaseException:` or `except Exception:` means forcing them to think about which one they actually mean. This is a good thing, just like the enforcement of proper separation between bytes and text is a good thing.
Only if they don't understand what 'raise' means. It's obvious this construct is just injecting some additional information in a passing exception, there's no issue if it catches everything.
It will change exception class if logging function will fail... I wouldn't call this "good chance", those kinds of things are pretty unlikely in my experience.
Bare except + reraise is a very common Python pattern, so no, it won't be assumed to be a bug. This was actually one of the major points in the discussion of the PEP that led to its rejection.
Java solved the problem by having Throwable as the root of all exceptions and not advertising that fact loudly. The derived Exception class is the root of all safely catchable exceptions. When someone catches a Throwable, something strange is going on.
> Bare excepts are syntactic sugar for `except BaseException`.
I'm guessing that a `3to4` script would be provided which replaces bare `except:` with `except BaseException:`. We have the experience of `2to3` to draw on with regards to how that might play out.
EDIT: Haha, I now see that this PEP proposes a change without advancing the major version. That surprises me.
I think Rich Hickeys advice of not breaking people applies here.
The anger from this potential change is that really all you are doing is taking something away that was working, and now people will need to review their code or keep python on a previous version which sucks.
I think that people who propose these kinds of changes don't appreciate the importance of the programming language being at the bottom of the stack so there's really never a good reason to break people even if you think it's nicer as you really can't appreciate how much work you are creating for people.
IMHO, working on a programming language is only for some, just like working on a database is only for some. The first rule should be: "you should never break the language, ever". Just like you should never break the database or kernel behavior.
This is why I like to stick to C, Common Lisp, Clojure, and (to some extent) Java/JVM. I don't know about Clojure's future, but C and Common Lisp have been fine for the last 40 years, and I'm not expecting the least in the upcoming years.
I half agree with this rule. I think that it's fine to break things as long as you make a semantic version change _and_ provide automated tooling for upgrading old code. If you can't build this tool, that is a strong negative signal for both versions of the language.
What I don't like about say, c, is that it has various backward compatible additive dialects like c11 vs c99. I personally don't agree that c11 and c99 are the same language in spite of the backwards compatibility and I think it makes the entire ecosystem worse. At some point there needs to be a successor rather than just piling on to old broken designs. I would prefer a better FFI or other tools to interface with legacy code in the new dialect.
> I half agree with this rule. I think that it's OK to break things as long as you make a semantic version change _and_ provide automated tooling for upgrading old code. If you can't build this tool, that is a strong negative signal for both versions of the language.
I'd be happy to find automated tooling for upgrading old code that actually works. Python 2to3 converter worked on simple code but would fail on anything mildly complex (what would that tell about the Python language then :P).
IMHO, breaking language could makes sense if you have built-in runtime (like golang) or you ship native binaries that depend solely on kernel or libc (C/C++). You build your program and ship it to the clients. But, when you have a runtime like Python, you must also force clients to upgrade it. And the problem with Python is that you can't easily do that because system stuff usually depends on a particular version (yum/dnf, ubuntu services, and so on). And, unlike Java, the Python community only advertises a little about having multiple Python versions installed simultaneously.
> What I don't like about say, c, is that it has various backward compatible additive dialects like c11 vs c99. ... At some point there needs to be a successor rather than just piling on to old broken designs.
This is why people are still using C. There are many successors with varying success (D, C++, Rust, Zig...) and much more added complexity to the language and runtime. Language simplicity, compilation speed, and compiler simplicity - all of that was lost.
One painful one that is still reverberating a bit in some areas is the renaming of "SafeConfigParser" to just "ConfigParser" in the standard library (in 3.12). This caused a whole lot of breaking in some areas because versioneer (a package for determining a package version from git tags) used it (in code that was placed inside your package, and so couldn't be solved by just upgrading versioneer).
Python 3.7.9 (default, Aug 23 2020, 00:57:53)
[Clang 10.0.1 ] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import cgi
>>>
Python 3.13.0 (main, Oct 8 2024, 01:04:00) [Clang 18.1.8 ] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import cgi
Traceback (most recent call last):
File "<python-input-0>", line 1, in <module>
import cgi
ModuleNotFoundError: No module named 'cgi'
>Thanks a lot for voicing your opinions and concerns! After reading carefully all the arguments, the poll and the different positions we have decided that the best course of action is to withdraw the PEP as there is clear agreement that the breakage doesn’t justify the benefits here.
What bothers me more on a meta level is that some people in the community are apparently empowered to just write PEPs on a whim that will be quickly withdrawn, whereas most people will be forced to fight through long discussion threads before finding a sponsor willing to co-sign to a PEP (if that ever happens). The "Ideas" section of the forum is probably the least pleasant to use; and for all the controversial new stuff that gets added, the dev team is stunningly conservative WRT ideas that come from outside.
In particular, they'll commonly tell you to demonstrate your feature as a third-party package on PyPI first, then show that it gains popularity (i.e. you as a random developer are responsible for promoting your idea, not merely justifying it) - and if you succeed at that, they'll have the argument waiting for you that you already have a maintained, mature library that's perfectly capable of working on its own, so why would it need to become part of the language? "The standard library is where packages go to die", don't you know?
BTW, they will also tell you these things if the nature of your idea makes it impossible - e.g., you propose to add a method to a builtin type; subtyping won't work, because part of the point is that literal values should get the functionality automatically.
So, after wasting everyone's time they issue a bureaucratic power talk statement that the casual reader will interpret as "nice". They certainly do know how to play mostly socially incompetent followers.
(These are the same people who humiliated Tim Peters and others.)
One must imagine Sisyphus happy. Python just loves to break working code on a regular basis with its new releases. If your code is protected from untrusted user data and the internet, Python 2 is actually a really nice language that doesn't constantly force rewrites.
Oh, you want to know the naive UTC datetime in Python, to interface with something like PostgreSQL that recommends naive times? Back in the old days, a simple datetime.datetime.utcnow(). Now days, you need something like:
try:
from datetime import UTC as tz_UTC
except ImportError:
from pytz import UTC as tz_UTC
dt = datetime.datetime.now(tz_UTC).replace(tzinfo=None)
> Oh, you want to know the naive UTC datetime in Python, to interface with something like PostgreSQL that recommends naive times?
Postgres never recommended naive datetimes. A TZ-aware datetime is semantiacally the same as a tuple of (<location/agreed offset>, <time in the moment since unix epoch defined in terms of UTC>). Those who recommended dropping the knowledge of the first part from that pair did it because they didn't know better.
It's a perfectly fine strategy in some situations to only store UTC in the database and handle time zones on display. It's your database, you know what's in there. As an added bonus it allows easily flagging non-UTC timestamps as errors on some level to make sure you don't get tangled up in time zones.
> It's your database, you know what's in there. As an added bonus it allows easily flagging non-UTC timestamps as errors on some level to make sure you don't get tangled up in time zones.
I think you're mixing rendering (and for that matter - time ordering) with the initial information providing, that is a precise data input that doesn't rely on anything external.
Postgres ALWAYS stores timestamps in UTC [1]. When you submit data for persistence from your application, you have a choice of either:
- informing the DB about the recorded location/offset at the point of persistence (even if it's the zero offset) so that the input conversion happens without any reliance on the underlying OS-level or configuration setting [2]
- or omitting that information and therefore 1) losing precision at the point of data input and 2) delegating the location/offset inference for the internal purpose of Postgres to the external global configuration: both side-effects are bad from consistency/reliability perspectives.
I've never seen a single application that would win anything from (2) compared to explicitly providing the offset of the observed moment, even if the entire business domain is in UTC at all times. In fact, I've seen many business applications that explicitly record the offset in a separate column next to the provided timestamps for any future analysis and retrieval. And it's partly by design of the underlying abstractions: the unix epoch is defined in terms of UTC too, so when you design your program around implicit UTC you are not gaining much - the offset information is still there, it's just your data types don't make it clearly visible to everyone. But the moment you start integrating your data with the real world that cares about global time ordering of your recorded data events, you get a whole bunch of silent mistakes and issues that you won't have enough data to fix reliably until you switch to explicit offsets in all timestamp evaluations.
You're describing a world that doesn't exist from my experience writing booking systems.
Getting time zone conversions right all the way through the stack is far from trivial, which is why I prefer to do it in one place and keep the rest of the system out of it.
> If your code is protected from untrusted user data and the internet, Python 2 is actually a really nice language that doesn't constantly force rewrites.
If Python 2 is acceptable for your use case, then you could stay on an old version of Python 3 just fine as well.
I think the point was making sure that code won’t break in the future. If you tell someone „use python 2 to run my script“, you know it’s going to work basically forever because the latest python 2 won’t be changed. That’s not true for python 3. I still think it’s a bad argument, but that’s what I understood the idea as.
That argument still seems inconsistent to me, since saying "use python 2, pin your dependences and never upgrade python so your script runs forever" is the same as "use python 3.12, pin your dependences and never upgrade python so your script runs forever".
Although 3.1 also meets that criteria. And even with python 2, the dependencies could still update. The main thing is that it's the not-updating that will make your code run forever (on any system, language, version), not the fact that it's python 2.
No you can't. For example I use a script to compress scanned PDFs by combining individually processed JBIG2 images and that script hasn't been updated for more than a decade: https://github.com/agl/jbig2enc/blob/master/pdf.py It works and generates perfectly good PDFs. No it doesn't work with Python 3 because it mixes bytes and strings copiously. I could spend half an hour upgrading it to work with Python 3 but there's no reason to.
Don't forget the whole reason why Python 3 exists is because it broke compatibility with Python 2. Plenty of old unmaintained scripts were forever stuck in Python 2. Not to mention an old version of Python 3 actually performs worse than Python 2.
> I could spend half an hour upgrading it to work with Python 3 but there's no reason to.
How about empowering people other than yourself to understand how the code works, rather than relying on them to decipher the precise way in which you "mixed bytes and strings copiously"?
What if someone else did it for you? Would you reject the PR on principle?
The code was not written by me. I merely found this code useful.
If the author, Adam, or someone else upgraded it I would be happy using it with Python 3. But the code works as is. I'm also happy using it with Python 2.
Nothing is untrusted. I trust my own scanner. It's just that it produces files that are too large. And when it is told to reduce file size, it reduces resolution instead of using good compression.
The way I was taught Python, you really, really don't want to use bare `except:`, because it catches _everything_: Ctrl-C interruptions, system exit, etc. Instead, you really ought to use `except Exception:` (where `Exception` is the base class for any "normal" runtime error).
So I definitely understand the rationale, but it's hard to say it's worth the pain of backward incompatibility - we have linters, style guides, etc. that can catch this.
Yes, I was bitten by it in the past. Still, it'd better be a lint, or at least a very very long deprecation period... like, deprecated and removed in Python 4 or something.
I steers people who don't know better away in numerous ways, e.g. most editors with language awareness will give some kind of visual indication like a strike-trough.
In other languages, such a change would only be possible with a major version bump, though I imagine that because of the Python 3 collective trauma, the language designers now are OK with breaking older code without calling it Python 4. Anything goes, as long as it's called Python 3.
(Python lost me in the 2->3 migration and I haven't used it in a decade, so correct me if I'm wrong)
I don’t think there have been significant backwards-incompatible changes in Python 3 the language. There have been some, e.g. async and await were first introduced as soft keywords and then switched to being actual ones. But that’s not all that different from the treatment of yield in Python 2.
(Recent stdlib changes have been much more destructive, but I’m assuming that, like in the original thread, we’re drawing a distinction between those and changes to the actual language.)
Full disclosure, I welcomed Python 3, because for me that was the first time (since 2.4 on Windows XP) that I could count on my programs not randomly shitting their pants upon encountering Cyrillic in files or filenames, which for a native speaker of Russian you can imagine is quite important. (The csv stdlib module in Python 2 did that, IIRC. Perhaps I was holding it wrong, but experience shows that absolutely everybody did.)
One that bit me was the change to StopIteration propagating through chained generators, following PEP 479. Nothing huge, but I had to patch some previously working code to accommodate the new language release.
I think basically every new python version removes some standard libs and marks new ones as deprecated (at least 3.13 did), that’s potentially breaking.
The bytecode format changes, too, as does the C ABI (although they're making improvements on the latter front - there's supposed to be a more stable subset now). But more recently, they committed to a proper scheduling procedure for standard library deprecations and removals.
It's still amazing to me that the old `csv` module expected you to open the file in binary mode. (This was fixed in 3.1 - the 3.0 release was honestly quite premature.)
Python doesn't use semantic versioning. The number after the first period is a major (annual) release and can and does contain breaking changes (though so far never on the scale of the 2->3 upgrade).
We may never see a 4.0 because of the scar tissue, but the language continues to evolve.
> We may never see a 4.0 because of the scar tissue, but the language continues to evolve.
They should do the opposite really. If it hurts, do it more often and get better at it. A perfect time would be when Python gets some nice JIT performance improvements which everyone will probably like.
Sane BDFL would have already stepped in with "Absolutely not" and PEP closed with "Will not implement"
EDIT: Add on, Private Email to Pablo going "Dude, why are you purposing stuff that will break code spectacularly? I think we need to talk about your approach to language design."
They are literally trying to depreciate a feature that regularly breaks user space because it’s unintentionally misused. I don’t think I’ve ever come across a use case where people actually intended to write a bare except. Anyone who ever does this are actually thinking that it’s effectively the same as “except Exception” which it isn’t, but that’s more obvious to most users.
"our users are idiots and we need to keep them away from sharp edges" is exactly what keeps driving me away from Python and pip. It's why I wrote https://pip.wtf -- Python package management would be so simple if they'd just stop adding more and more seatbelts and cushions to Python.
"seatbelts and cushions" is not how I'd describe a package manager that can run arbitrary code from the downloaded package when you explicitly tell it "please only download this", simply because it wants to verify that building it will result in it having name and version metadata that matches what you asked for (https://github.com/pypa/pip/issues/1884).
This is not fixed in 24.2 btw, even if you do everything according to the latest standards - you're still allowed and expected to have a setup.py if you choose Setuptools as your backend and you release an sdist with a non-trivial build step. 24.3 should be out some time this month and I'll be interested to see if they've finally done something about this issue, which has existed for almost the entire lifetime of Pip.
Not just considered in PEP 722 - the uv feature is just an implementation of PEP 723 [1] (PEP 722's successor/competitor), which was accepted. Other tools like pipx support it as well.
No kidding about pip. The dependency resolver change several years ago was a similar terrible move to the PEP being considered here. It broke so much legitimately working code for no good reason; just paternalism from the core devs. The change pushed my team to stop using pip at all for dependency management.
Hard disagree there. It was way too easy to get yourself into an incompatibility hell with the old resolver, where package A relied on transitive dependency X v1.2 and package B needed X v2.1. Which version of X you got depended on whether you installed A or B first.
Yes, the new version did mean I had to straighten out a few projects that were already working before, but they were working by coincidence because my code paths weren’t stumbling across the incompatibilities. The problem already existed. The new resolver just exposed it.
My case was different from yours. Our project wasn't working by coincidence, the dependency resolver was flagging incompatibilities that simply didn't apply to our case, and began refusing to build a stable working project. Yes it's more risky to override that kind of guardrail, that's why I would only do it when I know the risks and tradeoffs and determine it's the best course of action on balance. I strongly believe that tools should ultimately work for the user, over dogmatic principles.
I'm fine with the those safety guardrails being the default behavior, but removing any sort of escape hatch because the pip devs think that they know better than the users of the tool 100% of the time is what I object to.
In the end we ended up ditching pip entirely for this use case and ended up with a much better system, with absolutely no disasters as a result, but we had a burn a lot of time and angst that could have been spent on actual problems we were trying to solve.
> If you don’t want pip to actually resolve dependencies, use the --no-deps option. This is useful when you have a set of package versions that work together in reality, even though their metadata says that they conflict. For guidance on a long-term fix, read Dealing with dependency conflicts.
One example of a time I used a bare except was when I wanted a program to retry three times if it failed for any reason. I just wrapped everything in a for loop with a catchall except.
The problem occurred when our scheduling program (Airflow) noticed the program was taking too long to run and decided to kill it. It sent a kill signal to Python, which dutifully caught the exception, retried and continued to run. I had to add a special case to allow Airflow to kill the program.
This PEP just forced me to look up the difference between the classes Exception and BaseException. It turns out that BaseException includes every exception, whereas Exception excludes those that are trying to exit the program (like SystemExit and KeyboardInterrupt).
With a bare except, your code will continue to retry even if SystemExit or KeyboardInterrupt is raised. This is almost always a bug.
In other words, your comment is an argument for the proposal!
I don't think it's a good enough argument to make a backwards incompatible change. This is a wart Python has to live with now. But I do think it's a shame that bare excepts behave in a way that is almost always a bug.
Maybe bare excepts could be modified to just catch Exceptions. It seems like a reasonable expression of the idea: everything that could go with my program but not with the OS.
That seems less bad in the sense it would affect fewer people, but the ones it did affect would likely be much more strongly affected. For instance, I could imagine someone with an old daemon that had a too-level loop like:
while True:
try:
serve()
except:
log(‘oops’)
so that it was more or less bulletproof. This might be a highly unpleasant change for those people who counted on it running 24/7 and never dying.
In other words, the current behavior is a minor hassle for many people. That change would be a major hassle for a few.
I’d be all for a deprecation warning on bare excepts. That might nudge a lot of people to fix their code without actively breaking anything.
Personally I think that would have been a better choice in Python's original design, but to change it now would be a backwards-incompatible change, i.e. it suffers from the same big problem everyone is highlighting in the PEP.
Thank you for being honest about making the exact mistake they are trying to help out with this.
I’ll restate what I posted elsewhere, I have never come across anyone who actually wanted to write a bare except, only people who thought naively that it worked like “except Exception” actually does.
I understand people are grabbing pitchforks because everyone has at o e time or another written code using a bare except and they are thinking that this will break it, but they 100% intended that to work like “except exception” python could just make that the default for the bare exemption and keep the syntax.
Depreciating the behavior is a good choice, and anyone who actually wants to capture exit events or keyboard interrupt can explicitly capture those.
If code breaks with this depreciation it’s because someone is doing something extremely out of the ordinary but isn’t being explicit about it.
Valid reasons for backwards incompatible changes to language syntax:
1. The language guarantees have become inconsistent, and the syntax breaks security boundaries or realtime guarantees that the language explicitly promises, and it is unfixable.
2. The universe of all written code is small enough that there are guarantees the syntax is unused, or we can change all instances of the syntax in an atomic manner.
Invalid reasons for backwards incompatible language changes:
> Requiring specific exception types makes the programmer’s intentions clear and encourages thinking about what exceptions might occur.
This reeks of the same rationale for checked exceptions in Java. That was a failed experiment. You can't force people to deal with exceptions. They end up just swallowing them instead. It's better to propagate an exception than do that almost all the time.
Like will we see except: replaced with except object:? I don't even know if that's valid. It's never come up.
Second, this would be a breaking change. I really feel like this is where Python 3 went off the rails. In the Python 2 days making breaking changes was essentially verboten. But ever since Python 3 decided breaking changes were OK< it's like there's little restraint now and minor releases seems to be far too comfortable with this.
Going back more than a quarter century ago (!), I was one of those zealots in favor of checked exceptions in Java. It took a good 15 years to finally conclude the “checked” part is more trouble than it’s worth.
Given that there is inevitably a root context for any given language invocation (main() for a command li program, top of a thread for a multi threaded app, etc), unchecked exceptions and a top level handler have proven to be more than enough.
I won’t comment on Python backwards compat, will just shake my head.
I'm weakly opposed to the PEP, but if your concern is that you're going to lose the ability to catch all exceptions in new code, then that's wrong as discussed in the Backwards Compatibility section, i.e. do "except BaseException".
They explicitly describe the PEP as evil, is there a tradition in the Python community for having obviously terrible PEPs, just to document the reasons for not doing something? Because that would make this a lot more understandable.
> I figure if I make this PEP, we can then ask Guido to quickly reject it, and then when this argument next starts up again, we can say ‘Guido isn’t changing things to suit the tab-haters or the only-tabbers, so this conversation is a waste of time.’ ...
> This proposal, if accepted, will probably mean a heck of a lot of work for somebody. But since I don’t want it accepted, I don’t care.
If it's not a serious proposal then that's even worse, because there's a long discussion on that thread. So this non-serious proposal is just wasting people's time.
> Any literal composed entirely of M, D, C, L, X, V and I characters that does not follow this format will raise a syntax error, because explicit is better than implicit.
Good as a reminder that rules like “explicit is better than implicit” should not be followed all the way to the most absurd possible conclusions.
As many people have observed here, this is a couple of Steering Council members showing activity. Getting one's PEPs accepted has a totally inflated weight in the Python "community". The more, the better (by contrast very few people care about perfect and bug-free code).
So, if this thing is accepted, it pads the resume of certain people even more. And many software orgs will have one additional week of job security by rewriting existing code. It's a win-win situation.
Ever since the walrus operator coup Python has descended into madness and make-work initiatives.
I get that lately Python has decided it wants to be an industrial-grade enterprise programming language. But there's a part of me that misses when the Python community retained a "we're all adults here" ethos.
A bunch of people are mentioning the bugbear of the Python3 migration, but there's an important difference that makes the migration a lot simpler for a backward-incompatible change like this one proposed in PEP760:
You can just write code that is compatible with Python runtimes both before and after the change.
That means that you can use the same test suite, gradually getting the code more and more compatible with the new version, and you can switch your production runtime, without having to worry about a more complicated and involved rollback process.
Notably, in the Python 2->3 migration, it was not really possible (at first[*]) because "" (and b"") literals became -> b""
while u"" literals became -> ""
So, there was no way to write literals that would mean the same thing, and have the same type across the two versions
This is also the reason why libraries like six offered a `six.u` function (https://six.readthedocs.io/#binary-and-text-data) but that required banning use of non-ASCII codepoints in your string literals
[*] This was eventually addressed with PEP 414 (and of course, even with with PEP 414, the migration was not trivial)
> That means that you can use the same test suite, gradually getting the code more and more compatible with the new version, and you can switch your production runtime, without having to worry about a more complicated and involved rollback process.
This is all well and good for your own code, but it’s seldom the case the libraries. A new library release that ‘adds support for Python 3.14’ is very likely to include other changes in the same release that may or may not be trivial, even assuming you were already on the latest version of the library prior to needing to update. A change like this to the Python language might be trivial, but it would have a massive impact on the ecosystem.
> A new library release that ‘adds support for Python 3.14’ is very likely to include other changes in the same release that may or may not be trivial
There's no reason to believe that would be the case. The exceptions are:
- add support for Python 3.0 (that's of course a very different situation as I described before)
- eagerly adopt TONS of Python 3.14-only features (of course that means a different thing than "adds support for")
What a specific release claim to do is irrelevant. The library should run its test suite in CI against multiple Python versions.
in this specific case, updating to 3.14 just mean that the library stopped using bare excepts, which means that all version of Python pre-3.14 would still be able to use the library as usual (and thus, CI of those python versions can be retained).
If you're running a library with a version of Python against which it's not tested, of course you're in a precarious situation (and a bug that silently causes your code to behave differently is going to be a lot more tricky to deal with than a bit of syntax which is not supported anymore).
I just completed upgrading a monolith from Python 3.8 to 3.11 - no doubt many others in the same position with 3.8 going EOL. It was a monumental effort. I will say the huge majority of work was upgrading libraries that hadn’t been updated in years. I won’t go into the specifics of why we had chosen not to update these libraries earlier (unless there is interest), but I will say Python being as backward compatible as possible has huge real world value. More for the community and ecosystem than the language itself. For the people who care about PEP 760, they have their choice of linting tool to enforce this requirement.
I help maintain a small Python codebase at work (bulk of my work is in Verilog) and the number of times somebody's PEP science fair project has broken production code following a Python version upgrade is too damn high.
From an outsiders perspective: The ecosystem is a disaster, on a level even exceeding that of JavaScript IMO. The 2->3 transition was awful and lead to a rift in the python community for years (still causes issues 15 years later). Maintainers seem happy to introduce breaking changes without major version bumps. It's not that performant of a language (slower than modern Ruby, for example). Best thing it's got going for it is readability.
Python ecosystem is a disaster for certain reasons but there is simply no ecosystem better than Python for other reasons. It's a trade-off. For literally any niche problem you can find out there there will be a Python library somewhere in the annals of the internet. I use many many many programming languages and everything starts out as a Python script in my flow, because by the time you start half the code is already written.
It's the absolute best tool for "I need to programmatically do <thing> and will never touch this script again" or "I want to build a tiny utility app for myself and myself alone, and I don't want to have to pull in ANY dependencies or do ANY build steps"
I have literally used it instead of writing a curl one liner because I didn't feel like looking up the arguments.
Python is incredible for building tools, exactly like a small time machinist might build certain cutters for a part they are manufacturing, or a blacksmith build tools, or a welder building a jig, etc etc
I cannot fathom when people choose to build heavyweight or long lived applications and business products with it. Django is alright I guess, except that complicated database stuff will cause you problems eventually, and migrations are a lot of fuss for not as many guarantees as you would hope for the effort.
> The ecosystem is a disaster, on a level even exceeding that of JavaScript IMO.
I've never dealt with dependency issues with Python, but I've also not had to work with large projects with lots of dependences where conflicting transient dependencies became an issue.
> The 2->3 transition was awful and lead to a rift in the python community for years (still causes issues 15 years later)
At this point, this isn't really true. The 2->3 transition was a huge debacle, but at this point, it's water under the bridge. The only people still struggling are those that are stuck with a legacy code base written in 2.x.
> Maintainers seem happy to introduce breaking changes without major version bumps.
What's changed since 3.0 that's breaking, other than when async/await got added?
> It's not that performant of a language
I'll give you that. It's slow. But most would argue that if you're writing your core work and computations in Python, you're doing it wrong. Python is best treated as a glue between libraries written in a more performant language.
> Best thing it's got going for it is readability.
And there's the big reason for it.
Readability counts. It's huge. Code is read far more than it is written. Readability is why I consider Perl and Haskell as the antitheses of Python.
Basically, you choose Python when speed of development is more important than speed of execution.
Python is not my favorite programming language, but back in 2006 Python felt like a breath of fresh air for writing short programs. I felt more productive in Python than in C, C++, or Java, which were the other languages I knew at the time (I was an undergraduate CS student), and I still use Python as my first choice for short data processing scripts. In addition, Python has many libraries, built-in and external. When I worked as an AI researcher before returning to academia, Python was our team's main language since there's a rich ecosystem of numerical computing and machine learning libraries.
I agree, though, that the ecosystem has many rough edges, especially when it comes to package management, dependency management, and environments/containers. This is especially true in the AI ecosystem, especially as a researcher, where I had to deal with third-party code that is sometimes written by people who are solid scientists but have little software engineering experience.
I'm now back in academia as a teaching-oriented professor; it's been a few months now since I've had to use pip and conda, though I have written some Python scripts to aid with grading. I teach C++ and Haskell to undergrads now, and I also have side projects involving Scheme and Common Lisp :).
This seems silly. I hate backwards compatibility but this change will just cause people to use `except BaseException` everywhere which seems even less idiomatic than bare excepts.
ETA:
Nobody is going to dig through a large codebase to find exactly what exceptions can be bubbled up if they didn't design it with explicit exception handling in mind from the beginning. It will also potentially become a pattern people will copy in new code.
Fortunately the votes on the poll for this look to very much be against the PEP's proposal.
I don't mind Python being improved, but as we learned from the 2->3 transition it should not be changed in ways that break old code. All that will do is have people forever sitting on an old version of Python. That's a worse situation that having code with bare excepts.
> Currently, Python allows catching all exceptions with a bare except: clause, which can lead to overly broad exception handling and mask important errors.
The fact that this pattern catches NameError and other things which are obviously design errors means that it is a really bad behavior which is unfortunately common.
Of course, many folks in this comment section and the PEP discussion thread point out the pitfalls with the suggested remedies. It would be great if some amount of linting/warning/static check could be devised to help people uncover the problem though.
I see - I suppose that’s a fair viewpoint to have!
I’m not much of a python programmer but my experience with the language would make me tend to agree actually. There are bigger fish to fry and so the effort to go after this relatively tiny sardine is perhaps not worth it.
I don't understand this. To me, a bare exception is explicitly that. By not providing a specific exception, you're saying "nothing specific, literally anything".
The pep explicitly adresses the use case, and it's still allowed.
The point of the pep is that it should be explicit, especially because the short form doesn't show whether catching terminating exceptions is a bug or intentional
They really are hard to work with compared to languages where you declare which exceptions you'll throw,. I always get angry when pylint raises an error for catching over-broad exceptions[0]
Of course I'm catching broad exceptions because I have no idea what kind of exception is going to be thrown 12 dependencies deep and I don't want it to completely crash the program instead of letting me retry or do something else.
Yup this drives me crazy. I've been bitten by urllib3 or SSL exceptions being bubbled up by random libraries so many times that now I always include an except Exception: block just in case.
100% agree. Most of my bare except: are followed by import pdb;pdb.set_trace() so I can figure out what went wrong and then fix my code so that it never happens again, but I still leave it there because I I don't have time to consider the millions of ways my hastily thrown-together python script is going to fail nor do I want to game out how many different errors could happen. If Python would have been this hard to use 20 years ago, I wouldn't have been able to learn to program.
I switched to Go years ago because I know that code I wrote a decade ago is still going to work. And that guarantee is even more ironclad in recent years with modules and vendoring.
With a Go project, I can leave it to sit for 3+ years and then pick it back up and add a feature without any issues. I've never had that with a Python project. (and this isn't even about the 2-to-3 situation, I just mean minor version to minor version and packages)
Even though there are valid use cases for having catch-all clauses, I see people forget about properly handling SystemExit exception. If your service receives SIGTERM from the scheduler, you need to capture it and gracefully handle the shutdown instead of swallowing it.
Love the boldness here of trying to make something you dislike a syntax error.
I don't really get preferring `except BaseException` over `except`. I'd love to get more info on possible exceptions though and I think it would cut down a lot of the catch all handling I see.
As far as I know, python doesn't give you any way of asking "what exceptions can this function run" - aside from just inspecting every line of code. I'd massively love to have those kind of details available.
I have suggested adding an option for exceptions into typing as a non exhaustive list of what something can raise so you actually know what you should or shouldn't be handling.
But as it usually goes in the forums the discussion just devolved into misunderstanding it as checked exceptions and how code that'd benefit from it is bad because exceptions you don't know about should bubble up
Possibly relevant -- Nim has a compile-time warning for bare exceptions, initially enabled by default [1]. I think Nim-core changed their mind about this being a good idea a few months later: https://github.com/nim-lang/Nim/pull/21728 (though the message still says "The bare except clause is deprecated" if you do compile with --warning:BareExcept:on - I think the urge to actually deprecate has gone away).
I think for Python, rather than breaking bajillions of unpublished lines of code they should start with a more tentative & minimally invasive environment var/CLI switch opt-out warning that says something like "may be deprecated" where even the Python ./configure script lets you opt out at Python interpreter-compile-time. Measure the scope of the porting problem for a few years before trying to decide on The Plan for something that might be too disruptive.
Personally, I don't agree with this proposal. While yes, I agree, that bare excepts are often a source of bugs, I don't think it should be the language's responsibility to nanny the programmer on such things. To me, this seems to only reduce the functionality of the language. If explicit exception handling is necessary, let the programmer make that decision.
I love Python, but it seems to always get more complicated and more things in the core and now backward incompatible changes. Not so good. I love Python and how much I can get done, but I must admit I also love that I know the Go code I have from 7 years ago can run without problems. It is just more stable
I've spent a lot of time fixing/explaining python exceptions over the years, and I get pretty annoyed when I encounter bare exceptions. Exceptions themselves are so often misunderstood, it seems most people just take them at face value. However, do we really need to dull all the sharp edges and add guardrails to every fucking thing? In a corporate environment, sure, you can implement all the protections you like _without attempting to force your constraints on all users_.
If you care about types, safety, etc. there are plenty of fantastic projects that share your priorities, but they don't need to bleed into everything under the sun.
Sharing and adopting new ideas is healthy, but homogenization kills creativity.
Having a catchall exception may seem like a bad idea to some, but I am quite often in situations where I write code that I really don't want to fail (and I want it to continue running). Fore these cases I always used it as such:
try:
result = somethingrisky()
except SomeKnownException as e:
return handle_known_error(e)
except Exception as e:
log_error(e)
return None
return result
or something among those lines. And this is perfectly fine if that risky function e.g involves requests that in my experience can fail for a ton of reasons and it is hard to know them all.
I know what this is! They want to "to give it" to Jeff Bezos, by breaking AWS' boto3, that has a nightmare of a story for handling/catching specific exceptions. It's all politically motivated!
I'm not categorically against it, but it needs to go into Python4, not a minor revision. It breaks too much.
(Plus, the suggested transition easement, "A tool will be provided to automatically update code to replace bare except: with except BaseException:", indicates a fundamental flaw in this approach: it's still trivial for developers to catch-and-kill exceptions. So we're strictly increasing the verbosity of the language without actually solving the problem. :( ).
The existence of two opposing PEPs says to me that there may be a case to be made for either side, so we should default to not changing things.
I used to think it’s likely that you can make a much stronger case for one side, but I personally feel that’s more for early language development phase. If it’s been a certain way for a long time, it ought to be overwhelmingly obvious and overwhelmingly supported by the community to change it. And even then I feel a bit of doubt.
If you're referring to this PEP's "twin", i.e. "PEP 758 – Allow except and except* expressions without parentheses", that is not an opposing PEP. These two PEPs are orthogonal. One does not contradict the other. They are twins only in the sense that they are both about exception handling syntax.
A lot of comments are about this affecting existing unmaintained code and causing problems with upgrades.
But couldn’t the old code be automatically updated? E.g. wouldn’t replacing every ‘except:’ with ‘except BaseException:’ make old codebase compatible with the proposed change?
Sure, that’s a pain too and I’m not a fan of the change itself either; still, a breaking change that can be addressed automatically sounds relatively easy.
I really don't see what's even the problem with bare excepts. Yes, I do want to catch all exceptions sometimes. Sometimes, I don't even care what the exception is. I have ignored my linter's suggestion to fix it on multiple occasions. So, why? Ok, I'll use except BaseException then. How is it any better than bare except?
I don't remember there to be that much complaint about https://peps.python.org/pep-0352/ and from having worked on a large Python code base at that time, it was rather easy change.
This is how beautiful languages are ffed up. Can you leave the core syntax alone and let people write elegant code if they want to? Why do we try to force a certain type of coding down the throat of people. Ease of use made python popular and we should leave it that way.
I was afraid that this is something similar to infamous PEP 572 (also known as make-python-c), but it actually is a great change - it will remove errors and will make code more readable (contrary what PEP 572 did).
Maybe before enforcing such nonsense, they should standardize function docs like javadocs. How are you supposed to know which errors to "except" if it's not documented anywhere anyway?
I wish people would stop holding onto compatibility as if it is some amazing feature. It has benefits, but also comes with many drawbacks to innovation and improvement in established ecosystems
The proposal (not part of PEP 760) to add an implicit `raise` to the end of bare `except:` blocks would be far more damaging than making bare-excepts a syntax error.
> Now there’s an interesting idea: don’t make bare except illegal, make it have an implicit raise at the end (and disallow return, break, and continue).
...ahh, a bit of a misreading on my part. However, I was really looking for your explanation of the horrors that could happen rather than what the suggestion was. Thinking through a bit more, yeah, implicit re-raise does have some pretty bad outcomes if you can't change code in a dependency.
It still feels like `deno` is somewhat on the right track where permissions are dropped by default, but It'd Be Nice(tm) if programming languages enabled that a bit easier.
import sales_tax_calculator as xyz with [ cap.NETWORK, cap.FILESYSTEM, cap.USB, ...etc... ]
xyz.calculate( sales_price, state=user.address.state )
We're implicitly allowing imported libraries the full power of the containing programming language where with promiscuous code sharing (trending towards a low-trust environment), it'd be a lot better to _not_ give `cap.FS, NETWORK, USB, etc...` by default.
Bringing it back around: `import somelib with [ cap.BARE_EXCEPT, cap.RAISE ]` or something to control their handling of "unknown" exceptions is interesting. Let them handle any exceptions or interrupts they've authored, but let me explicitly have control over catching stuff that isn't "from them".
...an extended version of dependency injection or inversion of control.
Adding an implicit raise to the end of a bare-except would quietly break things, and is non-trivial to detect. Say you have a naive base-except:
def loop():
try:
check_service()
except:
logging.exception("Error while checking service.")
time.sleep(60)
Really you shouldn't be using a base-except here. You should at the bare minimum catch `Exception`. Adding an implicit `raise` at the end will break this function without so much as a warning. Instead of calling the function every minute, the loop is broken with an unexpected exception that was deliberately suppressed (and logged).
A more common scenario for myself is write a lot of my scripts in the style:
An implicit raise would obnoxiously break them when my bare-except is intentional, and effectively cause the error to be printed twice to the terminal. Now I'm not wholly opposed to forcing `except BaseException:` instead of `except:`, but an implicit raise would cause all sorts of subtle bugs.
This has to be some kind of joke. If it had gone the other way and python had originally required people to write "except BaseException", a PEP to allow a bare except would be an obvious improvement to the language, since it would allow people to do the same thing and save pointless typing. This proposal is suggesting we break virtually all existing Python code to make the language objectively worse.
I'm guessing this is a sort of "modest proposal" type parody.
I don't know why this is seen with such aversion. This is the language forcing sane coding practices, since you always include what should be cached. Also, the syntactic sugar for except BaseException is to not catch it in your `try ... except` clause. If you do, for example:
TypeError will bubble up. If you want to catch everything, and handle it, like the example from Mailman[0], you should catch the base exception anyways.
This can be solved and detected by static analysis tools anyways.
That's funny, I code a lot of python but I don't participate in any large projects where a pipeline might fail due to this. So I don't follow the PEP news much.
And yet I have created my own habit of not using bare excepts. TIL what bare except even means, but I do not use them. Simply because I think it makes more sense to specify the exception I want to catch, and failing that I specify the base class Exception.
So I guess I understand the author of this PEP, we're of one mind on this. :)
Also thanks to this post TIL that a bare except might catch interrupts like ctrl-c. Even more justification for my new habit.
Cant stand the infantilization of software tools. People can choose for themselves what features to use or not use. Doesnt need to be determined by our keepers. Also do Python maintainers just hate backwards compat as some sort of religion?
I'll add a 4th rationale:
4. It will create work for countless developers which is completely consistent with the python core value of disdain for other people's time.
If this pep were implemented I suspect it would result in forcing thousands of not tens of thousands of people to spend hours modifying perfectly working code and destroying the ability to run old scientific code without modification. Extremely effective industrial sabotage if it were to be accepted.
It is hard for me to articulate how much peps like this reinforce my desire to never start another python project. Even if this pep is rejected the fact that there are people who would put in the time and effort to write and submit such a PEP tells me that they will do it again, and eventually they might succeed.
> It is hard for me to articulate how much peps like this reinforce my desire to never start another python project
I completely understand this sentiment. Recent python events have made me wonder if there are some people intent on sabotaging the management of the language.
I loved the incremental improvements and thoughtful process involved up until a couple of years ago but it feels like python will become brittle and break badly if things continue the way they are. It feels like the adults have been driven out the room when it comes to stewardship. I'm not sure how recoverable the situation is.
It wasn't recent by Internet time but when the debate on walrus operator drove out the BDFL that was the obvious break. Python has been circling the drain ever since. A lot of motion, yes, but to what end?
- - - -
Oh! How could I forget!? The creeps actually banned Tim Peters!
Yes? and as GP said, he stepped down because (as explained in the first line of his email) "Now that PEP 572 is done, I don't ever want to have to fight so hard for a PEP and find that so many people despise my decisions."
A lot of the time, people say things like "Python has been circling the drain ever since." referring to the implementation of the "walrus operator", to imply that they don't like the feature and that it was the first of a series of changes to the language that have made it progressively worse; and they often further imply that if only we still had the original leadership then we could avoid such damage to the language.
I was, in a sense, in that camp at the time, before I looked it up. I felt that the operator went against the spirit of the language by trampling on what was previously a strong, and clearly very conscious, separation between statements and expressions. And I misguidedly imagined, and lamented, that GvR was unable to keep it out of the language, being overruled by consensus.
I just want to make sure it's clear that things are not like that. Rather, the Python envisioned (nowadays, though not originally) by the original leadership includes PEP 572 - and probably also the large majority of what's been added since.
It's not the "walrus operator" per se that's the problem, it's the change in project governance.
Python has been captured by bureaucracy and corporate interests. The way it's being improved-to-death is a symptom. The unceremonious eviction of Tim Peters indicates to me that the take-over is complete: the new guard feels comfortable throwing out the old guard, they expect that their power is such that no one will blink, and no one did. (No corporation withdrew support for them, the blow-back was all hot air.)
It would be interesting to know GvR's opinions of some of the new cruft, but it's not really relevant.
Using "|" to merge dictionaries (which was possible in other ways before) instead of offering pipes as in bash and Elixir (a feature that's actually useful).
The “|” operator was already used for set unions and binary OR, so it’s a little late to reserve it for control flow. Personally I don’t mind having a “dict union” operator at all.
On the Python side, though, at least you can build your own pipes! You can define various helper classes that have, say, an `__rrshift__` method, to let you do the following with full type-checking support:
Like all of python, `a | b` operator is just `a.__or__(b)`. If you want that operator to do something different in a different context, just override __or__.
Nothing prevents you from defining the | operator for other user-defined types where that would actually make sense. A dictionary doesn't represent an ongoing process or stream. Lots of things are possible; that isn't a reason not to find better ways to do them (cf. Raymond Hettinger).
Any Lisp-ness comes from Elixir being a skin on Erlang, and so comes almost anything else good to say about Elixir. (And my comment was explicitly comparing Elixir to Erlang.)
The 'pipe' is hacky, for example, because it's just syntactic sugar that only works in one specific case, and not in general.
I feel like as a scripting language Python excels. Glad to have this PEP, but it would be more pythonic have except be optional.
The reason I pick up Python for projects is because it grows with the application; opportunities to add typing etc. Who knows maybe in a few years Python will enforce all the types and it will be as verbose as Java. Personally I’d like to see how they handle declaring a method or function throws exceptions.
Pretty narly we have compiled Python apps with poetry, it’s starting to punch out of its weight class.
So it doesn't matter if it goes through or not, just that someone proposed a change like this is enough to steer you away from Python?
If the change goes through, couldn't you just use older Python versions for those specific projects, or has the Python ecosystem still not figured out how to do this without huge hassles?
Either software is updated or it isn't. If you're worried about "bitrot" then you bear the responsibility for your end of keeping the system up to date. (Or finding a third party to do it.) API Changes occur for a reason, and it isn't reasonable to expect other developers to make security fixes to their older versions of code in perpetuity while guaranteeing that stable interface in perpetuity. They'd never get to fix anything that isn't a security issue that way. Programmer resources are limited - especially for Python, which doesn't pay the overwhelming majority of its devs (although it can afford to pay several PSF staff).
Python is open source. Nothing prohibits you from forking the 2.7 codebase and adding your own security patches (or more substantial things like back-porting new OpenSSL support, or even cherry-picking backwards-compatible features from 3.x that you do like), for example.
I'm happy when people criticize new features in Python. But I expect to read criticism of features based on their actual merits and consequences, not on the principle that it's new or backwards-incompatible or would cause "churn".
So we have this proposed change which will cause a bunch of needless churn.
Then we have people saying "just don't upgrade anything if you don't want to deal with the churn" but that too is a very large burden for all the reasons I mentioned.
All the reasonable adults in the room want option 3: don't do the thing that causes needless churn.
> But I expect to read criticism of features based on their actual merits and consequences, not on the principle that it's new or backwards-incompatible or would cause "churn".
I am honestly shocked to see this attitude. Churn is a very real consequence. Do you have no respect for the time of the countless devs who use python?
The entire point is that proponents of the change disagree that it's "needless churn".
It's bizarre to me that expecting people to improve their code incrementally is considered a disrespect for their time.
The proposed change is not arbitrary - which can be seen by trying to imagine the alternate universe in which the reverse change were proposed. One can imagine a world in which `except:` were added to a Python that didn't support it, but certainly not one where it were made mandatory (whether for the `except BaseException:` case or the `except Exception:` case).
I assume there are people out there who would, similarly, argue that it was "needless" to make `print` into a function (and thereby break users of the `>>file` syntax). But it demonstrably and significantly makes the language better.
If you want to talk incrementalism, then the right approach is making this an error in other parts of the Python ecosystem. Linters, mypy, pyright, etc, without make a source-breaking language change.
Imagine whatever universe you want, but we live in this one. In this universe, there is a shitload of old python. Python 2.7 isn't even completely gone. Literal lifetimes of collective time would be spent on the fallout of this change, which does not seem worth the reward.
Does that matter when you just want to run "old scientific code"? Old version of libraries like OpenSSL can still be run in that context, granted you don't expose that code to the internet at large.
Old scientific code broke for many people with the introduction of the mac m1. I would think this would be a continuing trend in the future. Staying on old versions simply isn't possible over a long period without keeping the hardware going with it too.
> Old scientific code broke for many people with the introduction of the mac m1.
How could the people maintaining Python possibly avoid that? It would be up to Apple to proactively reach out to affected projects, if Apple cares about that.
Who finds "old scientific code" and then exposes a server running that code to the internet without any changes? Sounds like asking for trouble, but I guess we all use computers differently...
I don't know why people do things without thinking them through, but they do. Regarding trouble, I don't think we've covered anything here that wouldn't be asking for trouble.
Perhaps in a few years we can have another PEP, to require "except BaseException" to be replaced with bare "except:". Then we can all change our code back again.
If someone on my team or in my company proposed to break most of our python code for no substantial reason, unless they were pretty junior I would count that as a real red flag against their judgement.
How do people land on the python steering council exactly?
That's besides the point. I don't want to muck about with tools on my Python scripts.
I have sometimes not run a Python script for a few years, and then when I need it, it stopped working and I need to track down what changed/broke or run some tool or whatnot. I don't keep track of the latest greatest Python changes – like most Python programmers it's not my "day job" to write Python code so I now need to track what changed between "the Python version I used about 3 years ago, whatever that was" and now. It's pretty annoying.
And that's assuming said tool will be fool-proof. Never mind of course that all my dependencies (if any) will need updating too.
What will happen in practice is that people will write "except Exception:" rather than "except:" and do nothing different. Basically nothing will change.
Meanwhile, I have C and Go programs that have worked without modification for about 10 years. Not that nothing ever breaks in C or Go, but it's the exception (hah!) rather than the rule.
> like most Python programmers it's not my "day job" to write Python code
I’d love to know whether that’s true, and to what extreme. I believe you’re right - that people using Python for a few hours a week (or less) greatly outnumber software developers using it as their primary language.
I think that’s a real issue for the evolution of Python, because updates to the language design (e.g. the makeup of the Steering Council) come almost entirely from the second group.
> I think that’s a real issue for the evolution of Python, because updates to the language design (e.g. the makeup of the Steering Council) come almost entirely from the second group.
Yes I agree, and it's disappointing to see some take such a narrow view of things.
A big part of maintaining and evolving a language is saying "no" a lot. There are a lot of people with ideas, almost always reasoned from their own use-case. That's okay, everyone does that to some degree, but there almost always trade-offs and such to consider.
Your job as Steering Council or Core Dev or BFDL or whatever governance you have is to consider all use cases and make a balanced decision. Reading that thread, some do. But unfortunately others don't.
Even for Python 3, Guido spent most of his time saying "no" to proposals. There were a lot of pretty wild ideas for Python 3000.
Which just underscores the point that this is mostly software engineering theater. If your goal is a system in which all exceptions are explicitly and appropriately handled, your first mistake was picking Python.
I propose a rider to the PEP in which implementation will be deferred until its proponents can correctly affirm that the library reference lists, for each function and method, every exception it might throw.
>If your goal is a system in which all exceptions are explicitly and appropriately handled, your first mistake was picking Python.
No, the goal is a system in which the code correctly indicates which exceptions it's intended to handle, and doesn't accidentally handle the wrong exceptions because the developer was either lazy or misinformed about the semantics (perhaps due to experience with a different programming language).
> ...and doesn't accidentally handle the wrong exceptions...
What support does Python offer, to the author of a function, for determining what would be the wrong exceptions for it to handle? How, in Python, does the author of a function signal, to the functions she is calling, which exceptions they should not handle? As I alluded to in my previous post, the Python language and documentation does not even give programmers a good accounting of what exceptions the library functions might throw.
The point was about how the set of exceptions handled by `except:` might not match what the programmer expects, especially a programmer coming from a different language. It's not about knowing what kinds of exception the code in the `try` could raise and it isn't about telling a called function what to do.
Other languages, notably Java, have tried the "functions document what they can raise" idea. Everyone seems to hate the result.
Thank goodness there was 2to3 tool in the past. It made the migration to Python 3 so smooth and quick. /partial-s
I know it is not nearly on the same level, but people seriously overestimate the effort needed between not doing anything at all and even the slightest work, no matter how reliable and easy. The difference between nothing and anything is huge.
I feel like I've heard this argument countless times, and yet I'm never swayed by it. I've been using Python for about 20 years and I've never felt put out by the need to change anything to work with a new version of Python (or of a library). It simply hasn't caused significant pain - my memories are more filled with painful debugging sessions caused by overly-clever designs or trying to refactor too much at once.
Eh, while I sympathize with what you’re saying, PEPs get written and rejected all the time. I’ve gotten the impression that some were written for the main goal of documenting the reasons why a common request is a bad idea.
Like, I don’t know if there’s a PEP to use braces, but it wouldn’t surprise me if someone had made one so that from then on there’d be an official doc you could point people at when they ask about it.
Not saying this is one of those, and I see Brett Cannon’s on this one. I am saying not to get too worked up over the existence of a draft PEP.
I think emitting a warning every time an unspecific exception is caught might be a better balance. That way, you could still do a quick “try: … except: …” when drafting new code, but the code might warn you if the bare except block is ever used (including what exception was caught, and a suggestion for how to catch only that specific exception).
I often find discussions of these sorts, whether for python or other open source projects, get so focused on purity of concept that they totally forget practicality
On the bright side, turning bare exceptions into types exceptions is the kind of thing an llm is great for. It's also basically zero cost for new code.
On the other hand, I completely agree that it's not worth a breaking change.
There's some space for interpretation in picking exactly which exception type to use depending on context (value error vs runtime error vs not implemented error), and there may be package specific exceptions available.
Please don't cross into personal attack / name-calling, or post shallow dismissals of other people's work. As the site guidelines say, a good critical comment teaches us something.
For those Python users for whom writing python is the core of their work that might be fine. For all the other users for whom python is an foreign, incidental, but indispensable part of their work (scientists, analysts, ...) the choice is untenable. While python can and should strive to be a more 'serious', 'professional' language, it _must_ have respect and empathy for the latter camp. Elevating something that should be a linter rule to a language change ain't that.