Python Switch Statement
matteoguadrini.github.io
matteoguadrini.github.io
It was discussed on HN: https://news.ycombinator.com/item?id=26080760
So, maybe there is need for switch - some people think the language will be better with the match statement.
I get it that you can use "match" as if it were a "switch", but I think it's important to drill in every Python developer's head that they are not the same thing.
static NOT_FOUND: i32 = 404;
match 403 {
NOT_FOUND => println!("Not found"),
_ => println!("Other"),
}
It doesn't compile: error[E0530]: match bindings cannot shadow statics
It does compile if you change static to const (and then it does what you want).But if you misspell NOT_FOUND so it doesn't shadow a static then it misbehaves in the same way as the Python version, with only a few warnings to show for it.
Python has the limitation that there's no fundamental difference between ordinary variables and constants.
I don't know of an alternative proposal for Python that isn't burdensome or also dangerously surprising.
[0] https://twitter.com/brandon_rhodes/status/136022610839909990...
The match operator (I guess the new keyword means it can't be a module) seems to be a similarly well-considered additon to the enum.
Python appears to add common features after more front-end work than the competition.
That said, one fears the higher release tempo could lead to "featuritis". I still have yet to wield the walrus operator that was recently added in any code.
The right question is, “Is language X better for having a switch statement?”
Very few control structures are strictly necessary because it is almost always possible to achieve the same thing in a different way. For example, in C, you could rewrite this:
while (x < 10) {
// ... some code ...
}
To this: goto test;
begin:
// ... some code ...
test:
if (x < 10) {
goto begin;
}
The fact is, there are important differences between a map full of objects and a switch statement. One IMPORTANT difference is that if the switch branches contain references to variables the outer scope, then to convert the switch into a map, you have to construct the object at least once each time you enter that scope, and the cost of constructing it is proportional to the number of branches. Similarly, if you use a chain of if/else statements, then the cost of finding a branch selected uniformly is proportional to the number of branches.With a switch statement, there is normally some kind of assumption that the compiler is capable of optimizing a subset of these use cases—often a well-known or well-understood subset, and that the switch is fast—perhaps similar to a dictionary lookup or table lookup—yet without the cost of constructing a closure for each branch. Even a novice compiler developer can figure out how to make their compiler generate a table for integers or strings.
“I’ll just make this control structure out of lambdas” is something that’s theoretically sound, but which may require a much more sophisticated set of optimizations to get similar performance out of.
This is not a hypothetical concern—if you want to implement a lexical analyzer or parser, you often want to build it out of big switch statements, especially if the switch statements can be transformed at compile-time into table lookups or something similarly fast. The cost of parsing data, in my experience, can often be a significant fraction of the runtime of the entire program.
(If your response is, “Well, why write a parser in Python if you want it to be fast?”, then, well, keep in mind that CPython is not the only Python, and there are many reasons why you would want to write a parser in Python otherwise.)
Python (C) does not have the tabled indirect jump that would be required to convert the table of objects into a table of offsets thereby turning the function call into a jump.
The compiler could use such a tabled indirect jump for every switch statement. Some of which could be optimized by using a different type of table rather than always using a frozendict.
So, I had very carefully worded that statement. I said you have to construct them "each time you enter that scope."
Although CPython does not have a jump table opcode, it easily could, if it were a priority.
def bar(x):
def X(): return 1
def Y(): return 1
def Z(): return bar(x-1) + bar(x-2)
table = {0:X,0:Y}
default = Z
return table.get(x, default)()
Whereas I was thinking of: def X(x):
def X(): return 1
return X
def Y(x):
def Y(): return 1
return Y
def Z(x):
def Z(): return bar(x-1) + bar(x-2)
return Z
table = {0:X,1:Y}
default = Z
def bar(x):
return table.get(x, default)(x)()
Which can be reduced to a tabled indirect jump: def bar(x):
jump by x via table T and default D where T = {0:X,1:Y} and D = Z
X: return 1
Y: return 1
Z: return bar(x-1) + bar(x-2)One of the easiest way to identify a novice programmer is one who has very strong opinions about "the one" way to do things. Any deviation and their blood pressure starts to rise.
Anyway, your post was amusing.
It started out as one. Anyone remember the Zen of Python?
> There should be one-- and preferably only one --obvious way to do it.
I fled Perl. I did so for numerous reasons, but perhaps the most frustrating was that there were so many ways to do something, and trying to standardize on an idiom was frowned upon, given "TMTOWTDI." This, in practice, is a contributor to Perl code requiring more cognitive load to read and translate into "What was the programmer trying to do?" Other factors added in, of course, but Python was comparatively obvious at the time: you look at the line, it is clear what is going on.
As the number of language features in Python expand, Python as a culture must rapidly settle the "idiom" or lose the "obvious way." Just how many flow control statements do we need? Obviously there is a bare minimum for Turing completeness, and beyond that a sweet spot.
But I fear that Python may slowly become a kitchen-sink language with no idioms, and so coders would be reduced to some of the downsides of Perl.
Myself, I try not to have opinions about how to do a particular thing so much as I care about that there is a settled way of doing it. The more ways that are common, the more choices I have to make as a programmer, and this also raises my cognitive load. "What's the best here? Is it sufficiently obvious now? Will it be in the future?"
In short, yes, I would like some prescription. Lessons from the past and all.
def switch(match, dictionary, default="no match"):
for key in dictionary.keys():
if match in key:
switch.last_match = key
return dictionary.get(key)
return default
Using “in” rather than “==“ here seems odd to me, is that intentional?i.e I’d expect switch(‘abc’, {‘a’: 1}, default=“default”) to return “default”, but under this code it’d return 1, no?
switch('a', {'abc': 1}, 'default') #returns 1
... but you're not wrong, that's godawful. But from the context of the article, they're optimizing for the case where multiple cases evaluate to the same answer. It would make more sense to write switch('a', {('a','b','c'): 1}, 'default') #returns 1
I'd optimize for performance, personally: def switch(match, dictionary, default="no match"):
return dictionary.get(match, default)Every article talking about how Python doesn't have, need, or want a switch statement is now redundant.
https://docs.microsoft.com/en-us/dotnet/csharp/language-refe...
and
https://docs.microsoft.com/en-us/dotnet/csharp/language-refe...
Happy for it not to have fall-through.
I guess my_dict.get(myvar, default)() would be simple enough.
That said, I never really had a problem not having switch either.
It's very useful in times for us who use switch regularly. I have no skin in the python game though so just curious from an outside dev haha
Perhaps some languages conventions are different, but for example Douglas Crockford on JS:
> "switch Statement
A switch statement should be avoided, but when used should have this form:
switch (expression) {
case expression:
statements
default:
statements
}
Each case is aligned with the switch. This avoids over-indentation. A case label is not a statement, and should not be indented like one.Each group of statements (except the default) should end with break, return, or throw. Do not fall through."
My problem with languages trying to too strictly control stuff like this, is that this is stuff that should be handled in your code review process. Sometimes when working with old code, it's not really worth the time spent refactoring a whole switch when you need to make a small fix, vs adding a single line.
# where a and b are functions
FOO = { a.__name__: a, b.__name__: b }
and with argparse do parser = argparse.ArgumentParser()
parser.add_argument(
"-f",
"--foo",
choices=FOO.keys(),
default=next(iter(FOO)),
help="choose your type of foo",
)doubt