"Pattern matching is the switch-case equivalent in Python 3.10 and newer"
Well, yes, but actually, no.
A switch operator, at least in most other languages, uses comparison to select a branch. Only comparisons are performed (either with each branch or optimized for direct access) but no other side effect. Pattern matching, as with other languages too, tries to 'match' the variable to the pattern. If it succeeds, then the branch is taken and the variables (if any) are assigned, which in python means that they are assigned to the whole current function. This is a big side effect.
And that's the main problem, the variable constants. Once you understand the issue it is relatively easy to fix, but for a newcomer this will probably be a few minutes of searching and debugging.
Python don't have a switch statement, at least not yet, pattern matching is just another similar feature that can be adapted to work similarly, like the chain of elifs and the lambdas dictionary. However if you want to say otherwise...at least give the warning of the constants at the beginning, not at the end.
> If it succeeds, then the branch is taken and the variables (if any) are assigned, which in python means that they are assigned to the whole current function. This is a big side effect.
Assignment is not generally a side effect, and even if it were, matched branches in a switch statement potentially having side effects or externally visible variable (re)assignments is normal in languages with switch. (Ironically, match-based assignments visible outside the pattern match expression aren’t typical in languages with pattern matching, so actually the behavior you describe is more typical of switch statements than pattern matching, which is typically a block-scoped expression with local bindings.)
I don't really understand the logic behind this comment. You're pointing this out like it's some contradiction in people's opinion.
But it's not like "block scope" is the one and only determinant of whether a language is good or bad, so lack of that alone would negate the above statement.
js could be bad and python good, regardless of whether Python lacks block scope. Doubly so since js also lacks it by default, and has only sort of tacked-it on with let/const.
But the even better answer is that both are languages with different strenghts and tradeoffs, that JS did have several bad design decisions early on, that both have evolved, and x=bad, y=good is a fanboi reaction, not a developer reaction.
The rules for var and "hoisting" are so horrible that let and const are a justified language complication; now JavaScript variable scoping is decently usable. On the other hand, Python can afford to spend its language complication budget on something more useful than specialized mechanisms to end variable scope.
Since JavaScript tends to have many more unnamed functions defined in the middle of long functions, compared to Python (which tends towards named functions, classes, or comprehensions), block scoped variables (or more precisely simple and rigorous rules about scoping) are much more useful and important in JavaScript than they would be in Python, since if there is a part of a Python function where some variable shouldn't be visible it's probably a long and complex function which can and should have its variable scopes refactored apart.
Fanboi discussions and reactions are the norm in the Python community. But this what you get when maybe more than half of your community is made of non-devs, who never touched any other language and never will.
Ignoring the snark and elitism in this comment, it’s unclear to me to why even “actual developers” must touch other languages, if python satisfies their needs.
Programming is a tool, with the goal being building things that help the user.
> it's only for specific classes, you cannot change the behavior of 1 == "one"
More clearly because builtins are protected from monkey-patching, you can’t change the behavior of == where both operands are builtins. “Only for specific classes” implies the scope of applicability is much narrower than it is.
In practice I never try to replicate switch/case in Python but prefer what the author lists as "Dictionary" method.
Namely matching an item in a dictionary based on either key or perhaps more advanced matching a value inside the dict. Maybe even using re to match it.
Either way, I find using a dictionary is the most Pythonic and clean way to do it. Defining the actions first, how to match them and anything else they might need.
I believe this is more generally known as table-driven methods, and I also enjoy using them. They feel more ergonomic than conditional statements, and they're also more flexible—you can dynamically modify the conditions and actions by modifying the data structure which contains them.
I can't believe languages are still adding switch/match statements (as opposed to expressions). Has it it not filtered through to everyone yet that everything that can possibly be an expression should be?
> _Statement vs. Expression._ Some suggestions centered around the idea of making `match` an expression rather than a statement. However, this would fit poorly with Python's statement-oriented nature and lead to unusually long and complex expressions and the need to invent new syntactic constructs or break well established syntactic rules. An obvious consequence of match as an expression would be that case clauses could no longer have arbitrary blocks of code attached, but only a single expression. Overall, the strong limitations could in no way offset the slight simplification in some special use cases.
Python makes a conscious decision to embrace statement-oriented code, for better or worse. There's a case to be made that it makes things more readable in an intuitive sense, while of course it also opens you up to more statefulness/side-effects. If nothing else, I respect the dedication to consistency.
To give a concrete reason: Python's significant whitespace makes nested expressions much trickier to write/parse
Either everyone involved with language design has never heard of Lisp or there are inherent tradeoffs with "everything that can possibly be an expression should be" that you are either ignoring or wilfully ignorant of in order to score internet points.
> Either everyone involved with language design has never heard of Lisp or there are inherent tradeoffs with "everything that can possibly be an expression should be" that you are either ignoring or wilfully ignorant of in order to score internet points.
Entirely seriously, what are the trade-offs? I've heard general defences of it, for example nchammas's quote from van Rossum in a sister post to yours, but they all seem quite general—"it'll complicate things", but in a way that seems only to boil down to "it's unfamiliar and I don't like it." Which, to be fair, is van Rossum's prerogative to say—but it doesn't seem persuasive in light of general language design.
Some constructs are more natural (e.g. easier to read and write) as statements, others as expressions. It's never good to have rigid rules about programming style as each problem domain is different.
There are many situations where you want lots of branching because that's the problem domain, and trying to hide the essential aspect of the problem domain isn't helpful for code readability or maintainability.
If you are writing an interpreter, case statements are a natural thing. Yes, you can write that intepreter with expressions that are statements in disguise, but just because something can be done doesn't mean that it should be done. An interpreter is just a dispatcher waiting for some trigger from the outside and then firing off a command handler depending on the trigger. Just like a UI. GUIs are are also natural things to write with event driven patterns. If user clicks on box A, then do X, if User clicks on box B, do Y. That is a big case statement in disguise except the underlying framework handles that for you and you just write the code for X and Y. But if you didn't have that UI toolkit doing heavy lifting you would find yourself writing event loops and case statements to implement it as the most natural (non-ideological) solution.
Similarly, there are many situations where you want to handle exceptional conditions separately in a clear way that allows you to easily understand what the exceptional logic is and change it without touching the main processing flow at all. It all depends on the problem domain. Or the problem domain may require feedback where the handler tells the dispatcher to change the dispatch algorithm.
On the other hand, fluent APIs work well with streams of data that are processed sequentially. There the natural approach is to apply a sequence of functions to the stream. You want to avoid lots of nesting of case statements as the code would be ugly.
It boils down to minimizing complexity. You could write that event driven dispatcher as a function that takes as its inputs all the possible callbacks and the state of the world and spits out a specific callback. But that would be a more complex program that is harder to read and write, and thus is more error prone, regardless of the fact that in theory it is easier to refactor and unit test by dropping in a different function. In practice, it is not easier, it is harder. So when someone like Guido says, "it will complicate things", no it doesn't boil down to "I'm unfamiliar and don't like it", what it means is "I've spent a couple of decades writing code, have been up and down this road more times than you have, and I know it's a harder road to travel".
> Some constructs are more natural (e.g. easier to read and write) as statements, others as expressions.
Yes but imo, Haskell (which has do notation) and Rust (where most things are expressions, but you still write them as expression statements when writing imperative code) actually have solutions to this problem. On the other hand, Python has a sharp divide between expressions and statements, so it needs strange duplicate forms of statements as expressions, like ternaries (if expressions but in a really weird order!) and lambdas (function expressions but your body is just an expression!)
> So when someone like Guido says, "it will complicate things"
Yes, it would complicate Python, which already has this division. In other languages, it's perfectly simple.
Sure, in an existing language like Python, it can be a poor fit to make everything an expression, but what are the tradeoffs for a new language? For example, Rust makes (almost) everything an expression, and I don't think it's suffered at all for it.
I'm really worried for the long term health of python.
The philosophy of python is there should be only one pythonic way of doing things.
But more and more features are being added that give too many options with minor benefit
How is case different from the elif pattern? What does the walrus operator do that we really need.
Each one of these things on their own if fine but I'm worried together it's going to make python increasingly idiosyncratic to each developer and, consequently, harder to read.
Python's Zen beat out perl's TMTOWTDI. But python seems to try to be more and more like perl with every release: more appealing to hackers playing code golf than long term readers of the code base
Pattern matching is often conflated with switch-case by people who are unfamiliar, because pattern matching subsumes switch-case, but they're very much not the same thing. Structural pattern matching is much more powerful than checking a series of boolean conditions. It allows you to not only check the structure of a value, but bind parts of that structure to variables. It allows you to express logic that would would otherwise be fairly complex in a straightforward and transparent way.
Python already has (limited) pattern matching. Example:
for foo, (bar, baz) in nested_tuple_list:
...
This works as expected. The current 3.10 implementation is just half-baked. They should have expanded the existing pattern matching facilities (added support for data structure destructuring).
Presumably then you could just have used the existing walrus operator inside the already existing if-else constructs.
That would have been the obvious path of least surprise. What we got instead seems inadequate.
match command.split():
case ["north"] | ["go", "north"]:
current_room = current_room.neighbor("north")
case ["get", obj] | ["pick", "up", obj] | ["pick", obj, "up"]:
... # Code for picking up the given object
In to this:
if (["north"] | ["go", "north"] := command.split()):
current_room = current_room.neighbor("north")
elif (["get", obj] | ["pick", "up", obj] | ["pick", obj, "up"] := command.split()):
... # Code for picking up the given object
And the walrus operator would assign to 0-many values as required? It kind of breaks it's current meaning and makes the lines very complex, I don't think this version would be more liked...
The example is misconceived to begin with? Why are you hard coding an interactive fiction game in Python? You should make an interactive fiction engine, of which there are already a million examples, and then write the script in a language made for IF, not Python.
Appealing to nothing but the zen of python is not an acceptable argument.
Pattern matching is not (necessarily) extremely complex as it exists in a multitude of languages these days. Powerful does not automatically imply complex.
To many in the core team, for one. It’s a cutesy easter egg at best not a mission statement.
I’ve read the pep. The nature of peps are complete by design. The concept is not overly complex especially since it exists in a number of other languages.
I would say it's much more powerful than switch-case, but it's only moderately more ergonomic than elif. The reason for the former being that switch-case only ever checks exact equivalency; if you need to do anything slightly more nuanced, you have to bail out to something more flexible.
Yeah, Perl’s been there a long time ago. Initially, Perl did not have a native switch/case statement. However, early on Perlers started to obsess about emulated switch constructs and so Perl 5.10 shipped given/when which was supposed to become the language‘s official switch implementation. But it never really quite took of. While part of it was due to a subtle bug I‘d argue nobody actually needed it. 99% of the time if/elsif/else is good enough and otherwise you might want to consider a dispatch table if the number of cases is large. Beyond Perl this is true in most languages: If you find yourself in a situation to need an extensive if/else chain and to wish for C-like switch/case you’re either writing a parser or you should really rethink about your software‘s architecture (maybe in terms of OOP).
See though, this is the issue. What you see as a useful or mistaken feature is likely a different set to another user, just as we’ve seen above.
I think it’s a mistake to defer to the zen of python without considering the PEPs themselves which do discuss trade offs. (I’ll note that you didn’t make that claim but GP did).
Language features of this type should be nice and not game changing. It's how spoken word languages evolve. If every change was game-changing, it'd only be a few mutations until a dialect became a language in its own right. In reality, you get countless dialects frequently with features that eventually die off and new languages rarely and only in the aggregate.
Seems to me the 'sweet spot' for a language is a careful balance of continuing to evolve - slowly, carefully - and to not become so large as to 'not fit into one head'. Python3.9 is not yet at the 'too big for one head'; but 3.xx could be. Perhaps old-style features need to be removed, and only available via some sort of "from __past__ import old-feature-set"
It's hard for me to separate a language from what it is used to accomplish, and from that perspective, Python has been far too large to fit in one head for a good long time. Perhaps that is why I don't find any compulsion for language purity.
The trick is tell when striving for consistensy is foolish and when it isn't. Otherwise you would always be able to fall back on this quote to justify creating unuseable code/frameworks/projects/languages.
I get it, languages have to grow or fix mistakes. I read about Python long before I attempted to program in it and even I (not a language designer or that special) knew that their handling of division as being integer division as a default was a long-term problem that would eventually be changed one way or another. And so it was.
I absolutely agree on the walrus operator. It's too terse and while on its own it only makes two steps into one, it's not used alone, making each line of code with it that much harder to unpack.
I don't know enough about pattern matching but my feeling is that we got along without it so far ...
> The philosophy of python is there should be only one pythonic way of doing things.
That ship has sailed more than a decade ago.
(And if we're on a crusade to make Python pythonic again, let's start with the bigger fish - like the sync/async function distinction. Somehow Python managed to fall into the red/blue function trap the hardest.)
I think it’s pretty wild that people are worried about the addition of a coding pattern to a language. What a thing to stress about. Nobody’s taking your Python away from you. I promise everything’s gonna be ok.
> The philosophy of python is there should be only one pythonic way of doing things.
Thiiis really hasn't been my experience with the Python community. Every place I've worked on a Python codebase has done it a different way, and none of them have agreed on what is "pythonic".
Each team has claimed that _their_ approach is the most python-y way to do it, but they've all celebrated Python as not being "restrictive" like "other" languages. It's really weird.
> Thiiis really hasn't been my experience with the Python community. Every place I've worked on a Python codebase has done it a different way, and none of them have agreed on what is "pythonic".
Exactly my experience from day one. It's a laughable statement that the Python community keeps repeated with almost no basis in reality.
Compared to C++ and Perl I find python code to be really consistent across projects. I agree the language isn't perfect at it but why do anything to make it worse?
Now people are getting worried Python may become like Perl :).
With all due respect to your (otherwise perfectly valid) argument, "there's more than one way to do it" was never a real problem with people using Perl. I dare say it's fully possible to write difficult-to-read code in any language.
Perl was expressly never interested in erecting glass walls and force people this or that way. Not saying that this is good or bad. But it's the way the people who are making Perl want it.
Too bad ruby didn't take the place of python for popularity but I still see strong fan base around ruby.
Maybe people got into python when Google took it for a serious use just like people got into git because of Linus but figured it wasn't the best tool a decade later but the ecosystem is already there it is too late to swap.
The Zen is not inconsistent with there being different obvious ways to do different, closely related things, and Python has always had those.
The Zen is against alternatives for the sake of alternatives, but not for facilities focussed on different use cases. Though as the use cases get more finely divided, the difference is less peeceptible.
There's one that's even closer to c-like switches: a list/tuple of functions or values, indexed by a small int. If you're feeling extremely cheeky, it can even handle negative cases. You'll have to implement fallthrough yourself, though.
I've seen it used in ways that make sense. They were small and well-contained, and definitely didn't involve computing a hash.
For a not-small example, if you want to switch on an ascii character in a parser, you can make a 256-element list to implement your jump table, and index into it with ord.
If you're performance-minded enough for this to make a lick of sense, you'll take care to only build the jump table once.
And yeah, chained if/else is cleaner because it's the only tool that the language provides. The GP was asking for alternatives. They exist, but you'll hate them.
Granted, you can speed the 256-long if/elif mess up with a binary if/else tree 8 levels deep. But it probably won't beat the jump table.
If you truly find this upsetting, know that it's a practice that results from the language not supporting a switch statement.
I'm not sure that the second option is a very pythonic way to do it, but that's subjective of course. I'd imagine the original commenter views using a lot of ifs and elifs as the pythonic way to do it
Not the parent commenter, but I use that pattern all the time, mainly to match CLI flags/args/options to an action.
Say you have a program like "docker-compose". It has multiple subcommands. Each subcommand is a dict string key and the appropriate action to take is its value, a callable. This also plays well with argparse's parser "choices" argument.
The by far biggest downside is then that all callables mustn't take arguments, or take all the same ones. You cannot dispatch to a callable and dynamically assign what arguments to call it with without losing the prior benefits. Then it gets awkward (like having to throw around args and kwargs all over the place). But up to that point, I find the pattern useful in this case.
Now, every two releases they add a "language feature". Some library maintainers jump in the bandwagon, and WOOSH! : half the ecosystem depends on CPython to run. Bye Jython, Cython, PyPy, Micropython, etc and bye the code analysis and code transformation tools : all outdated until more funds or voluntary dev are willing to fix them
In an ideal world, the community should fork Python 2.7, rename it and add type hints. But that's too late, all the dev time was spent on making libraries compatible only with some recent versions of CPython
And another person might not care at all for type hints, consider them detrimental and against the spirit of pure dynamic Python, and ask for their favorite feature (e.g. async, unicode handling, whatever) to be the "one" feature "they should have added".
My perspective on this is totally different. I don't like python because I find it to be a simple language. I like python because it's powerful, expressive, flexible, and gets out of my way while I express my intent. I quite like rust as well (for different reasons), and after using pattern matching in it, I am so excited to see it in Python. I can't think of a single change to the language that has been made that I would consider questionable, but I keep hearing complaints about them. Nothing specific to the features themselves though, the complaints are less targeted about how specifically this feature does not belong in the language as a whole, but just general noises about how peoole are worried about changes. It's quite tiresome, I must say, to have it come up in literally every discussion of Python on this forum.
I too am getting tired of having to tell people to make sure they fully read and understood my point before replying. Just had another such incident recently. But bigger point is, there is such a trend to make fly-by comments, maybe with the aim of showing off or putting down the person to who they were replying. IOW, pretty much trolling, even if done unconsciously. Or just mental laziness.
Anyway, sick of arguing about such meta points. That is not what I come to HN for. I come for intelligent discourse via which I can learn from others' experience and share mine.
I'll just end by saying that I made specific points in that Twitter thread, such as BDD and RDD influencing the language's direction undesirably. To me, that is specific enough a complaint. A language is much more than just a bag of features.
YMMV.
It seems that your comment was targeted towards me, but it failed to desribe my behavior at all, so I'm left not totally sure that it was targeted at me. If so, I don't know why you have the impression that I didn't read what you wrote (even you Twitter thread) or that I'm somehow trolling. You have no specific complaints whatsoever here or in your Twitter thread apart from complexity and implying that new steering committee are just trying to pad their resume. How are generic insults specific criticism?
How do the language features not fit? What does "influence undesirably" actually mean? What language features specifically have been added that do not fit, and how have they made the language worse or harder to read or use? That would be an interesting conversation to have.
You have said multiple things compactly in a couple of paragraphs in your above comment. I think at least one or two are wrong, or at the very least, misunderstandings or misinterpretations of things I said - and said in pretty clear and simple language, so it surprises me that you got them wrong. But I don't want to reply right now, because I want to check well your replies vis-a-vis my statements, see who I think is right or wrong, and why, point by point, and then give one single consolidated answer, and also want to revisit my tweets to GvR and recollect what specific background incidents I saw, motivated me to make those statements. I only half recollect right now, since it is a while. But I know there were some reasons, because even if they were my impressions, those were on the basis of facts I had observed.
But one thing I can say right now is that you are wrong on two points: it was not insults[1], and it was not directed at the steering committee.
[1] Saying that something I said is an insult, without clearly understanding it, is itself an insult - by you, to me :) Do you really think I have nothing better to do than go around insulting Python people? Wrong! Heck, I am one of them. Although not on the core team, I have done a fair amount of work in the Python ecosystem, outside of the core language, have blogged a lot about Python (with many programming example, got consulting and training work directly attributable to my blog and my published Python software - the clients said this), and have published a couple of articles about Python in sites and magazines. I've also consulted to companies on Python and trained people and orgs on it.
Those two points you were wrong on (what I said just above the footnote "[1]") are clear from my tweets themselves. So you clearly have misinterpreted, or not read the tweet thread fully. An irrefutable fact: there are at least 9 tweets in that thread, apart from GvR's original one at the top, with many by me, and two or so by Cameron Laird, another veteran Pythonista, who is much more known than me (to some people), for many more years than me, for all his work, including tons of articles, and getting a community award. Check them out, including his thanking me for "that cogent distinction":
Some of your statements clearly show you either did not read or did not grok some simple points I made in them.
For example, you repeatedly talked in both your first and second reply to me, questioning me on specifics of what language features and their overall fit I thought were wrong, although I have not said a single word in my tweets there or comments here, about any specific feature being wrong or not fitting. (I only talked about other points, like RDD and BDD, and overall language complexity, caused by steadily adding many features to the language. (Cameron later agreed on that, too, in a tweet, after I clarified a point of mine he got wrong.) And you don't get to unilaterally decide what arguments about Python are relevant. If so, I can think the same. (As in, "It's, like, just your opinion, man".) If you keep insisting on questioning me on language features and fit, when my arguments are about other issues, then you are clearly wrong, misunderstanding me, or trolling.
More complete answer after a while. in fact, I've already said many of my points above, but will add any needed others.
I know my answer here was long, but I claim Pascal's Amendment on that :) Making it more concise or better worded, needs more time, which I don't want to spend on this matter right now.
You don't like it because it's simple? I can somewhat relate, but imagine if everyone adopted that mindset. The world would be a complicated, toxic mess. Simplicity is beautiful!
I love how Scala does case match with case classes. You can essentially unpack the attributes of a case class and match on them and put additional guards. Example:
bob match {
case Person("bob", _) => println("hi bob!")
case _ => println("I don't know you")
}
I think its important that match returns something so you can use outside of a function which this doesn't do.
val greeting = bob match { ... }
I thought it would be a fun exercise to write case classes in python, kind of like an improved immutable data class with a match that returns.
the "problem" with scala and case classes is that it's very cumbersone when you have case classes with 30+ fields (and you'll have it, expecially if they came from some SQL table) and you want to match only a sigle field.
as they are positional-only - and you also need to specify all the other placeholders, makes reading the match very difficult
“ It will take a long time until any library code can safely adapt this new functionality since Python 3.9 will reach its end of life approximately in October 2025”
Not true - for example, Black had been requiring python 3.6+ even before 3.5 and older were fully deprecated / unsupported.
Just declare the minimum version your library requires in setup.py and let users decide.
So switch like statements and branched execution are the enemy of caches and especially in the light of recent CPU predictive execution vulnerabilities I guess that is not an ideal syntax. What’s a better way to guide the programs flow based on some input?
There’s only 1 month left before the 3.10 feature freeze. I’d be surprised if the `match` statement gets anything more than minor tweaks before it’s released.
What is meant to be the benefit of not explicitly writing down types in python?
I have used C, C++, GL, and C# most of my career, but recently I have been helping a friend of mine who is learning to code with her python assignments.
Things still have types when the code runs, and type errors can cause all sorts of bugs and problems, but the types are not actually written down by you in the code so u have to remember them all. It just seems to add so much more mental load on the programmer + potential for weird hard to find bugs not being caught by compiler and slipping under the radar.
It seems objectively worse to me. But I assume there is supposed to be some benefit. What is it supposed to be?
PS Bonus comment: whitespace controlling the flow of the program? Lets make characters that are invisible really important to how the program runs. Thats just plain dumb, that could only be the result of a weird fetish of the originator of the language.
One of the biggest strengths of switch statement, falling through to the next case, unless there's a break, is missing. Of course, I can't think of a good example right now, but I've used it.
Of course, I've also used switch statements in do { .. } while (false), so maybe I just need to be voted off the island.
Well, yes, but actually, no.
A switch operator, at least in most other languages, uses comparison to select a branch. Only comparisons are performed (either with each branch or optimized for direct access) but no other side effect. Pattern matching, as with other languages too, tries to 'match' the variable to the pattern. If it succeeds, then the branch is taken and the variables (if any) are assigned, which in python means that they are assigned to the whole current function. This is a big side effect.
And that's the main problem, the variable constants. Once you understand the issue it is relatively easy to fix, but for a newcomer this will probably be a few minutes of searching and debugging.
Python don't have a switch statement, at least not yet, pattern matching is just another similar feature that can be adapted to work similarly, like the chain of elifs and the lambdas dictionary. However if you want to say otherwise...at least give the warning of the constants at the beginning, not at the end.