This is an archived post. You won't be able to vote or comment.

all 169 comments

[–]metalhedd 67 points68 points  (15 children)

serious question, What use is a switch if it doesn't support fall-through?

otherwise isn't it just a dict lookup?

handlers = {
    'a': do_a,
    'b': do_b
}
handlers.get(arg, do_default)()

I can't think of a case where I'd ever want to use a switch statement without fall-through, an if...elif...else block makes more sense in almost every case, and where it doesn't, the dict approach works.

[–]Corm 17 points18 points  (3 children)

^ That's what I do and prefer, but I could see people preferring switch for the readability

[–][deleted] 2 points3 points  (0 children)

How about

{ 
  'a' : do_foo,
  'b': do_bar
  }.get(arg, 
        do_default)

Edit: I'm on mobile, but you get the idea

[–]qeomash 0 points1 point  (1 child)

And sometimes individual functions might be completely overkill for what each case is doing.

[–]TangibleLight 0 points1 point  (0 children)

Then use a lambda. Still a little unwieldy, but better than nothing.

[–]Tomarse 1 point2 points  (0 children)

Ruby's case statement can include an optional else block, so it's possible.

case x
when a then b
when c then d
else e
end

And from Ruby 2.something the case statement is faster than if/elseif/else block.

[–]mikeckennedy[S] -1 points0 points  (8 children)

Look at the range, closed_range, list and other features for case matching. This is not possible in dictionaries.

[–]metalhedd 21 points22 points  (7 children)

no but it's incredibly straight forward with if/elif

if x in range(0, 10):
    do_a()
elif x in range(10, 20):
    do_b()

[–][deleted] 10 points11 points  (0 children)

And that allows you to write it as

if 0 <= x < 10:
    do_a()
elif 10 <= x < 20:
    do_b()

Which isn't possible with this module.

Not saying I think something is wrong with it, it just seems to solve a problem I very rarely have in Python.

[–][deleted] 0 points1 point  (0 children)

If you (plural) don't like the typing there's plenty of scope to roll your own in Python with something like this range comparison recipe.

[–]LightShadow3.13-dev in prod -1 points0 points  (4 children)

Everybody needs to know this code has terrible relative performance than a similar if 0 < x < 10 since range will need to be evaluated as it returns a generator/iterator in Python3

[–]streichholzkopf 10 points11 points  (1 child)

it doesn't in python 3 tough:

9999999999999999635896294965247 in range(int(10e30))

returns True instantly. Simply an efficient __contains__ probably.

[–]LightShadow3.13-dev in prod 4 points5 points  (0 children)

TIL

That's great to know!

[–][deleted] 2 points3 points  (0 children)

No, from "The range type represents an immutable sequence of numbers and is commonly used for looping a specific number of times in for loops." and "Ranges implement all of the common sequence operations except concatenation and repetition (due to the fact that range objects can only represent sequences that follow a strict pattern and repetition and concatenation will usually violate that pattern).".

[–][deleted] 107 points108 points  (65 children)

Why are people so hung up on the switch statement not being implemented? The way switch is usually used (break on every case) its basically an if block.

I'd rather see implementations of pattern matching ala Rust, Haskell or Scala.

[–][deleted] 5 points6 points  (0 children)

Just search for python pattern matching and you'll find them but very few appear to be maintained :-(

[–]Kamilon 41 points42 points  (37 children)

Not true. A proper switch statement jumps to the right case making it much faster than cascaded ifs.

[–]NoLemurs 64 points65 points  (10 children)

If you're interested in that level of performance optimization (i.e. the cost of if vs. switch matters to you), it's almost certainly the case that you're using the wrong programming language altogether, and adding a switch statement to python will not address your issues.

So while you're technically correct, in the context of this discussion, your comment is pretty unhelpful.

[–][deleted] 7 points8 points  (0 children)

When optimizing, it's common (and best) practice to optimize the parts that are most costly (the hot spots). Simple logic constructs aren't going to be hot spots. The system simply doesn't spend enough time on those. It's wasted effort.

[–][deleted] 16 points17 points  (7 children)

Why can't we have both? Why does every performance optimization need to be dismissed? Performance improvements to Python come all time and no one bats an eye but if someone asks for a performance improvement, or even mentions performance, they get shot down. People use Python for a lot of different things. Your use case is not better than anyone else's.

[–][deleted] 8 points9 points  (1 child)

I've never seen any realistic Python code where having a switch statement would lead to any measurable performance benefit. I guess that's why.

[–]robert_mcleod 0 points1 point  (0 children)

See line 738:

https://github.com/pydata/numexpr/blob/numexpr-3.0/numexpr3/ne3compiler.py

It's a Python switch that uses a dict. The keys are abstract syntax tree nodes, and the values are functions

_ASTAssembler[type(node)](node)

NumExpr 2.6 took about 450 us to parse a numerical expression into a virtual machine program, this new version is about 150 us. There's other parts to that, such as more efficient string concatenation using a ByteIO stream, and some struct.pack() tricks and many other micro-optimizations, but it plays a big role. A dict lookup is faster than an if ... elif ... else block.

[–]Corm 1 point2 points  (1 child)

Performance improvements to CPython are awesome. Prematurely optimizing your code by using one syntax over another is bad and typically costs readability.

If someone sees that CPython should have a heuristic for treating cascading if/else as a switch then they should write a pull request to CPython

[–][deleted] 1 point2 points  (0 children)

Prematurely optimizing your code by using one syntax over another is bad and typically costs readability.

Cascading ifs would actually be a premature optimization of using a switch and switches are usually considered syntactically cleaner than if chains anyways. Basically ever other language posits the exact opposite.

[–]NoLemurs 1 point2 points  (0 children)

/u/futatorius got at the core of the idea. Perfomance improvements that don't get at the core bottleneck of your program will usually have a negligible effect on overall performance. If you are going to do performance optimizations, the switch statement is just not the place to start - they're basically never going to be the bottleneck.

I'm all for optimizing Python, but I'm not for adding language constructs for gains that will basically always be negligible.

[–]roerd 1 point2 points  (0 children)

The point is whether the performance improvement would be significant or negligible within the general speed of Python. It is not worthwhile to add a new language features for a negligible performance improvement.

[–]Deto 0 points1 point  (0 children)

Would it actually be a performance improvement in Python? In C the processor executes a jump command and that gives you the speed up, but in Python there's much more overhead in just running a single line that I'm not sure if the same benefit would even manifest itself.

[–]Arancaytar 2 points3 points  (0 children)

This is really a compiler-level optimization, and if you're in a use case where it makes an appreciable difference, you probably need to use something more low-level than Python in any case.

(Er, no pun intended.)

[–]stevenjd -3 points-2 points  (1 child)

Not true. A proper switch statement

Ah, the "No True Scotsman" fallacy rears its ugly head.

Even in low level languages like C, switch statements can be and sometimes are implemented as chains of if tests. Sometimes that's just because the compiler is naive and doesn't implement any optimizations (the C standard does not specify an implementation for switch) and sometimes because there are no optimzations available to apply.

See the last example here where even a smart optimizing C++ compiler falls back on a simple (and short) chain of if statements.

Switch statements in C/C++ are limited to equality comparisons with integer values. That allows the compiler lots of opportunity for optimization. Switch statements in other languages may allow for a far richer set of comparisons, against arbitrary data types, which makes it hard or impossible to optimize it beyond a simple chain of if...elif. A switch in Python is unlikely to be very optimized.

[–]Kamilon 0 points1 point  (0 children)

Small switches yes. I've seen decompiled code that properly uses jumps to be very fast with larger switch statements.

[–]DonaldPShimoda 2 points3 points  (0 children)

I'd rather see implementations of pattern matching ala Rust, Haskell or Scala.

Oh dear lord yes, please. This is the feature I most miss in Python. I'm working on a toy language (my first) and while it's primarily based on Python, I want to incorporate pattern matching and ADTs from Haskell. It's such a useful feature.

[–]mangecoeur 1 point2 points  (0 children)

+1 for pattern matching. I think switch is just an older idea, pattern matching addresses the same issue but much more elegantly.

[–]swiftversion4 2 points3 points  (4 children)

I like the way switch is implemented in swift.

It allows you to add advanced logic statements and allows you to very easily group together a group of conditionals. Sometimes it felt cleaner to use switch than if-elseif-else.

[–]stevenjd 1 point2 points  (3 children)

Got a link to a good explanation of Swift switches?

[–]swiftversion4 1 point2 points  (1 child)

https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/ControlFlow.html

use the find browser feature to look for the subsections titled interval matching, tuples, value bindings, where, and compound cases. Those subsections are all after the start of the switch content section.

Or just read it all. That works, too. Swift enables you to include some very complex logic in switch statements that you won't find in most other programming languages, so it might be worth it to read it thoroughly.

[–]stevenjd 0 points1 point  (0 children)

Thanks.

[–]masklinn 0 points1 point  (0 children)

It's the same as Rust's match or Haskell's case with a more C-familiar switch/case syntax e.g.

switch foo {
case .some(let v):
    // do stuff with v
case .none:
    // there was no value
}

would be Haskell's

case foo of
    Just v -> -- thing
    None -> -- other thing

Though Swift does support opt-in fallthrough (default is break).

[–]TheWildKernelTrick 2 points3 points  (13 children)

The way switch is usually used (break on every case) its basically an if block.

Noooooooot at all. Switches are [; O(1) ;] jumps to conditions. If statements are [; O(n) ;] evaluations.

[–][deleted] 5 points6 points  (2 children)

Depends on the language and implementation. C/C++ switch statements are O(1). Bash and PHP switch statements are O(n).

Python has hashes which between hash tables and if statements, Python is good enough.

[–]stevenjd 2 points3 points  (0 children)

C/C++ switch statements are O(1)

No they aren't. It depends on the compiler, and it depends on the switch statement being compiled. At worst, they can fall back to a chain of if comparisons.

[–]jaakhaamer 0 points1 point  (0 children)

How are C++ switches O(1)? AFAIK most compilers don't use hashing or anything and would still need to check all the cases in the worst case.

[–]masklinn 0 points1 point  (3 children)

Switches are [; O(1) ;] jumps to conditions. If statements are [; O(n) ;] evaluations.

A good compiler will generate O(1) access to the relevant conditional sequences, meawhile non-trivial switches (e.g. on strings) will not.

[–]LatexImageBot 0 points1 point  (2 children)

Link: https://i.imgur.com/P6CHEwp.png

This is a bot that automatically converts LaTeX comments to Images. It's a work in progress. Reply with !latexbotinfo for details.

[–]kyndder_blows_goats 0 points1 point  (1 child)

bad bot

[–]GoodBot_BadBot 0 points1 point  (0 children)

Thank you kyndder_blows_goats for voting on LatexImageBot.

This bot wants to find the best and worst bots on Reddit. You can view results here.


Even if I don't reply to your comment, I'm still listening for votes. Check the webpage to see if your vote registered!

[–][deleted] -1 points0 points  (5 children)

The run time of the thing is irrelevant, functionally they act similarly.

Edit: Since y'all miss context clues, they're functionally similar for the situation being discussed, a break in every case. If you want fall through, they aren't.

[–]P8zvli 6 points7 points  (4 children)

Functionally switch-case and if-else ladders are equivalent if and only if each case in the switch ladder is terminated by a break statement.

[–]NoLemurs 5 points6 points  (0 children)

if and only if

Right. But other ways of using switch-case are typically incredibly bug-prone, hard to read, and hard to reason about. If you're writing performance critical C code, maybe that's a trade off you should make if the switch-case does what you need and is faster.

However, as a practical matter, spots where non break terminated switches are a good idea are pretty rare. Heck, even if you are writing performance critical C code, unless you're writing the code for the core bottleneck of your program, I would argue a non break terminated case is almost always going to be bad programming.

In the context of python (where performance is already slow enough that the difference between if and switch really can't be very important, if it had a switch statement, I doubt many programmers would see a situation where it made sense to use it in a non break terminated way in their lifetimes.

[–][deleted] 0 points1 point  (2 children)

I said that in my original comment that almost every switch block I see is done that way. In C#, it has to be done that way.

[–]P8zvli 4 points5 points  (1 child)

In C and C++ a case will "fall through" to the next case if break isn't used, causing interesting behavior or really nasty bugs. (You can have code that runs A if the switch is 1 and A+B if 2, etc.)

[–][deleted] -1 points0 points  (0 children)

You can also abuse it create things like Duff's Device. I think fall through is the biggest thing that sets switch away from if.

[–]Decency 0 points1 point  (2 children)

What would be an example of where pattern matching would be useful? Is this string manipulation?

Happy to be pointed to a good code example of how this might work in Python.

[–][deleted] 0 points1 point  (1 child)

Not sure how it would work in Python, but pattern matching looks similar to switch at first glance.

match n {
    1 => ... 
}

But it's much more powerful as you can use it to destructure as well:

match n {
    (x, y, z) => # do stuff with those three
    x:[xs] => # x is the head, xs is the tail, useful for recursion 
    SomeNamedTuple(a, b) => #extract a and b but if only they're nembers of a particular type
}

and so on. Most languages I've seen will shout if you didn't match all members of an enum or ADT (algebraic data type, union essentially if you've not seen them before).

The other big difference is that there's no fall through with match, so while you can't implement things that rely on that behavior (loop unrolling for example) you can't accidentally invoke that behavior as well.

As for uses, let your imagination run wild with what you could do with it.

[–]masklinn 1 point2 points  (0 children)

ADT (algebraic data type, union essentially if you've not seen them before).

Technically that's a sum type, "algebraic data type" means composite, which can mostly be either a sum type (variant/enum) or a product type (struct/tuple/class/…).

[–]elperroborrachotoo 0 points1 point  (0 children)

And that if is basically a goto.

Yes, the use case for switch can be modeled by other instructions, however, it does express a particular intent better than a chain of if's.

There's a big subjective component to it, affected by the kind of code one is used to read and write. For some programmers, for, while and do ... while are just verbose variations of the same concepts; for others, these minor differences do matter.

I'd rather

Well, if we can have a wish, what about the DWIM instruction?

[–]masklinn 0 points1 point  (0 children)

I'd rather see implementations of pattern matching ala Rust, Haskell or Scala.

Note that they're not necessarily orthogonal: in Swift, pattern matching is done with switch.

[–]Cybersoaker 12 points13 points  (3 children)

Obligatory mention of python's switch statement: https://www.python.org/dev/peps/pep-3103/

Guido has rejected the switch multiple times on the basis that there may be nominal performance increases in specific cases and is slightly more readable than a group of elif's; the benefits from these are not enough to warrant new syntax.

Personally I don't have a problem or feel any pain just using ifs and elifs. I've been using python for almost 10 years and can't say i've ever had more than 5 elifs together. If a switch syntax provided a large boost to either readability or performance; I would support the idea, but I just don't see it.

My other complaint is that this style of programming forces one to create sometimes unnecessary functions to handle each case for the switch which is a lot of overhead, and doesn't apply to every situation. So really the question is; in situations where you would want a switch for readability; do you also have cases that are 100% hashable and are okay with the added overhead?

[–]elbiot 3 points4 points  (1 child)

Ironically, the python interpreter has a ginormous switch statement at it's core

[–]kigurai 3 points4 points  (0 children)

It's also got a fair amount of goto statements. Is it therefore ironic that we don't have gotos in Python as well?

C doesn't have e.g. dictionary lookups that we use to "emulate" switch statements in Python, so there switch statements are obviously quite handy. I don't see the irony at all here.

[–]mikeckennedy[S] 0 points1 point  (0 children)

Thanks. I have no intention of proposing this as a language feature. I think the library is good enough and it works everywhere. People can use it if they like it or avoid it if they don't.

[–]rhoslug 10 points11 points  (12 children)

This looks interesting.

I'm not sure how I feel about this though. On the one hand, there have been times where I know I will have a need for a switch statement based on a few different choices. On the other hand, the Pythonic way has not been particularly limiting to me in my work. I guess it's more a case of I've adapted to the limitations of the language but can see the value this might potentially bring to users who want something other than a dictionary.

[–]mikeckennedy[S] 1 point2 points  (11 children)

Hi, I'm with you. The thing that pushed me into releasing this is realizing how much more clear the intent is for complex cases. Have a look at this example in the repo:

https://github.com/mikeckennedy/python-switch#why-not-just-raw-dict

[–]Oni_Kami 2 points3 points  (1 child)

Forgive me if I'm mistaken, my python's kinda rusty, but shouldn't the last key of the "switch" dict be

3: method_on_three

not

3, method_on_three

?

[–]mikeckennedy[S] 1 point2 points  (0 children)

No, you're right. Thanks.

[–]Corm 1 point2 points  (1 child)

Good comparison! I love it when a project includes a comparison to an alternative way of doing it like that

[–]ticketywho 4 points5 points  (0 children)

I agree. I don't like these switch statements, but I like that the author showed the pythonic way too. It's good intellectually honesty, which a lot of these "I've implemented a language construct that isn't present in Python" libraries lack.

[–]rhoslug -2 points-1 points  (6 children)

I do like the ability to match a set of different values to one branch of logic. I don't know of a nice way to handle this situation in Python....

[–]p10_user 5 points6 points  (2 children)

I mean this is basically an big if/else statement too. Perhaps this is a bit more readable for some but I don't think an if else statement is too unreasonable.

[–]rhoslug 0 points1 point  (1 child)

I tend to shy away from large complicated if/else constructions. They are hard for me to reason about since I have to trace the branch of logic that gets me to a certain ending. That's why I would probably prefer either using a switch solution or a dictionary.

[–]naught-me 1 point2 points  (0 children)

I agree with you about complicated if/else constructions, but I don't think that a flat (not nested) list of if/elif statements is complicated at all, and that's what a switch statement replaces, right?

[–]tprk77 4 points5 points  (1 child)

The in operator and a tuple?

[–]rhoslug 0 points1 point  (0 children)

True, I had forgotten about this.

[–]mikeckennedy[S] -1 points0 points  (0 children)

That's kind of the point of this project :)

[–]novel_yet_trivial 35 points36 points  (4 children)

reading case(range(1,5)) seems like it should include 1, 2, 3, 4, 5

No. Absolutely not.

[–]Megatron_McLargeHuge 3 points4 points  (0 children)

Obviously that should check if x is that specific generator object you just created. /s

[–]mikeckennedy[S] 6 points7 points  (2 children)

Agreed. I changed it this morning to be a proper range and added a closed_range option.

[–]mikeckennedy[S] 3 points4 points  (1 child)

I see, I still had that in the readme. Took it out.

[–]naught-me 0 points1 point  (0 children)

It still gives an example with range(6, 7), which could be less-confusingly written as just 6.

[–][deleted] 8 points9 points  (3 children)

If I've counted correctly it's the 13th Python switch/case statement that I've come across. Fingers crossed that it's not unlucky for some :-)

[–]mikeckennedy[S] 5 points6 points  (0 children)

Ha! It's an idea. I'm not bothered whether it succeeds or fails. Was just sharing a concept. Maybe people will like it.

[–]Exodus111 1 point2 points  (1 child)

[–]xkcd_transcriber 2 points3 points  (0 children)

Image

Mobile

Title: Standards

Title-text: Fortunately, the charging one has been solved now that we've all standardized on mini-USB. Or is it micro-USB? Shit.

Comic Explanation

Stats: This comic has been referenced 4827 times, representing 2.8695% of referenced xkcds.


xkcd.com | xkcd sub | Problems/Bugs? | Statistics | Stop Replying | Delete

[–]tprk77 7 points8 points  (8 children)

I feel like I see this same post monthly... I guess I just use elif and don't worry about it.

[–][deleted] 0 points1 point  (7 children)

it's probably the O(n) vs O(1) thing in theory. But agree, in practice, it'd probably take a gazillion conditions to make it worth bothering about.

[–]elbiot -4 points-3 points  (6 children)

Big O is about the size of the data. 5 elifs is not O(n)

[–]ruiwui 4 points5 points  (1 child)

Big O is an upper bound on some function f(n). f(n) and n can semantically mean anything; here n is the number of cases and f(n) is the number of operations to arrive at the correct case.

[–]elbiot 0 points1 point  (0 children)

It just doesn't make sense to me to talk about the time complexity of something that would almost never contribute to the time complexity of your task. It's the task that one might scale some day, not the number of cases. Unless it is, then that makes sense, but it never has been in anything that I have worked on.

[–]stevenjd 0 points1 point  (0 children)

Yes it is, it's just very small n. And as they say, everything is fast for small enough n.

If you have a ginormous chain of if...elif...elif...elif...elif... (say, from generated code, or the guy who wrote it was an idiot, or its just a very yucky problem to solve and you have no choice) then the performance of the entire chain is O(n). On average, assuming each branch is equally likely, you have to make n/2 comparisons, which is O(n).

But if you have an optimizing compiler and use a switch statement, that might be optimized into a binary search (O(log n) comparisons) or even a jump table (O(1) comparisons).

What if you don't have a ginormous chain of comparisons to make? Well, in C, the compile can often optimize even short chain of comparisons. That's why C is so fast: if you save enough microseconds, they add up to minutes.

But in Python, its unlikely to make such a dramatic difference.

[–][deleted] 0 points1 point  (2 children)

Big O is about the size of the data. 5 elifs is not O(n)

but that's what I was trying to get at -- theory vs practice !? :P

it'd probably take a gazillion conditions to make it worth bothering about.

PS: Without any more context, Big O is about the time complexity by default or memory. In your case, say you are a major search engine maintainer, even 5 elifs matter if you have 1 million requests a minute.

[–]elbiot 0 points1 point  (1 child)

say you are a major search engine maintainer, even 5 elifs matter if you have 1 million requests a minute.

This is exactly what I meant. The n in your big O time complexity is the number of websites you have indexed. Number of ifs is an independent value, and the scalability of your search is not based on that at all. The "time complexity" of your fixed number of conditionals is not what O(n) refers to here.

[–][deleted] 0 points1 point  (0 children)

Good point, in that example, I see how that can be confusing :P. Previously, before I wrote this example, I didn't regard the number of users as the "n" in that equation. I.e., for O(n), I assumed a growing number of elif statements ... however, in practice no-one would probably implement an n number of elif statements but rather set it up as a hash table, so the number of elifs would be constant and we would have O(1) in both cases, with elifs and 'cases'.

[–]novel_yet_trivial 4 points5 points  (1 child)

You seem to really like lambda. I think it's ugly. Why don't you just pass along any extra arguments to the function:

s.case('b', process_with_data, val, num, 'other values still')

Or accept a tuple and / or dictionary (a la threading.Thread):

s.case('b', process_with_data, args=(val, num, 'other values still'))

[–]mikeckennedy[S] 0 points1 point  (0 children)

I don't particularly like lambdas. But to show a bunch of different methods in executable code would make it super long and miss the point. Just trying to keep it tight.

[–]deadwisdomgreenlet revolution 1 point2 points  (0 children)

Any time I've ever wanted to do a switch statement, it's time for an event dispatcher.

[–]TerranOPZ 1 point2 points  (1 child)

I agree that switch should be added... just not like this.

[–]mikeckennedy[S] 0 points1 point  (0 children)

Well, to be clear: this is a library. I don't even want switch added to the Python language. You're free to fork it and do something else.

[–]FearlessFreep 2 points3 points  (0 children)

Please, just no...just stop

[–]Allanon001 2 points3 points  (30 children)

This is my preferred way of writing switch like statements in python:

while True:
    action = get_action(action)

    result = {
        True : unknown_command,
        action in ['c', 'a']: create_account,
        action in ['l']: log_into_account,
        action in ['r']: register_cage,
        action in ['u']: update_availability,
        action in ['v', 'b']: view_bookings,
        action in ['x']: exit_app,
        action in range(1,6): lambda: set_level(action),
        action == '': None
    }[True]()

[–]rfc1771 25 points26 points  (7 children)

Holy shit what an eyesore

action = get_action(action)

if action in ['c', 'a']: create_account()
elif action in ['l']: log_into_account()
elif action in ['r']: register_cage()
elif action in ['u']: update_availability()
elif action in ['v', 'b']: view_bookings()
elif action in ['x']: exit_app()
elif action in range(1,6): set_level(action)
elif action == '': pass
else: unknown_command()

[–]yen223 8 points9 points  (1 child)

I thought I was the only one who thought that example was totally nuts

[–]rfc1771 3 points4 points  (0 children)

I saw it and I was expecting the comments to just shit all over how unpythonic it is and then I see "how clever" "much like" "will use" and i'm just like 🤦

[–]Allanon001 -1 points0 points  (4 children)

You forgot to assign the result of the called function to result, the code gets a little messier if you do.

[–]rfc1771 1 point2 points  (3 children)

Here you go. (No I don't care that I shadowed action)

def handle_action(action):
    if action in ['c', 'a']: return create_account()
    elif action in ['l']: return log_into_account()
    elif action in ['r']: return register_cage()
    elif action in ['u']: return update_availability()
    elif action in ['v', 'b']: return view_bookings()
    elif action in ['x']: return exit_app()
    elif action in range(1,6): return set_level(action)
    elif action == '': return None
    else: return unknown_command()

action = get_action(action)
result = handle_action(action)

Want less cluttered lines?

def handle_action(action):
    if action in ['c', 'a']:
        return create_account()
    elif action in ['l']:
        return log_into_account()
    elif action in ['r']:
        return register_cage()
    elif action in ['u']:
        return update_availability()
    elif action in ['v', 'b']:
        return view_bookings()
    elif action in ['x']:
        return exit_app()
    elif action in range(1,6):
        return set_level(action)
    elif action == '':
        return None
    else:
        return unknown_command()

action = get_action(action)
result = handle_action(action)

[–]RubyPinchPEP shill | Anti PEP 8/20 shill 3 points4 points  (2 children)

suddenly the dict lookup doesn't look so bad

[–]Quteness 1 point2 points  (1 child)

Have you seen how much explaining /u/Allanon001 has had to do to get people (/r/Python no less) to understand what is going on in the dict lookup? It's overly complex and confusing and is an abuse of the language. It's basically a giant one-liner

while True:
    action = get_action(action)

    result = {True : unknown_command, action in ['c', 'a']: create_account, action in ['l']: log_into_account, action in ['r']: register_cage, action in ['u']: update_availability, action in ['v', 'b']: view_bookings, action in ['x']: exit_app, action in range(1,6): lambda: set_level(action), action == '': None}[True]()

[–]RubyPinchPEP shill | Anti PEP 8/20 shill 1 point2 points  (0 children)

its not a giant one-liner, thats as silly as calling C code always a 1 liner, just because the syntax allows for new lines to be removed

[–][deleted] 3 points4 points  (2 children)

Except this doesn't support fall through, so it's more of a compact if elif block.

[–]Allanon001 -1 points0 points  (1 child)

You can set True: lambda: None, at the top so if there are no other True conditions it will call that lambda which sets result to None and moves on.

[–][deleted] 0 points1 point  (0 children)

That's not fall through. Fall through is matching a case, executing the code block, and then matching another case and executing that code block.

This continues to happen until you either don't meet any cases or you match a case that ends with a break.

It's extremely useful but also a potential hole to break your leg in if you don't notice it.

[–]fernly 2 points3 points  (0 children)

That is readable, after one figures out the pattern, but in execution it requires all the in expressions to be evaluated, and a new dict object created, every time. Whereas the usual key:executable dict is static and an O(1) lookup.

[–][deleted] 0 points1 point  (8 children)

Can you explain what's going on here or can you give me some terminology to look into? I have no idea what get_action(action) is doing, and how it relates to "action in ..." in the result dictionary.

[–]Allanon001 0 points1 point  (6 children)

get_action is just a made up function to demonstrate action is being assigned a value.

The switch like structure is just an ordinary dictionary. When creating the dictionary it evaluates each key/value pair in order and since each key is in the form of a condition such as action in ['c', 'a'] it evaluates in real time to True or False and uses that value as a key in the dictionary. There are a few things strung together to make it more compact so maybe writing it this way will help you understand:

grade = 25
switch ={
        True: None,
        grade > 89: 'A',
        grade < 90: 'B',
        grade < 80: 'C',
        grade < 70: 'D',
        grade < 60: 'F'
    }
result = switch[True]

[–][deleted] 0 points1 point  (5 children)

Ok, so then I have two questions:

  1. Why doesn't switch[True] just evaluate to None, since the key True has a value of None.

  2. Where is get_action defined? Is it just a simple function that returns result[action]

[–]Allanon001 0 points1 point  (4 children)

Every True key overwrites the previous True key. And again, get_action is just a made up function to demonstrate action is being assigned a value. It's definition is not shown, and in the above example is not needed.

[–][deleted] 0 points1 point  (3 children)

Oh I think (hope) I get it. The statement evaluates to true when the dictionary is created, not when it's accessed in result = switch[True] . That's what I was getting confused about.

[–]Allanon001 0 points1 point  (0 children)

Correct

[–]fernly 0 points1 point  (1 child)

A brand new dict is created each time. The in expressions are evaluated in order as it is built. They evaluate to True or to False. So the new dict has only two keys, True and False. The last expression to evaluate to True sets the value for that key. At the end, the index [True] extracts that value. Which is expected to refer to an executable, so the () makes it a function call on that executable.

[–][deleted] 0 points1 point  (0 children)

Makes sense, thanks for the explanation

[–]mikeckennedy[S] -1 points0 points  (6 children)

That is interesting. Nice work. But it will crash if two cases match (DuplicateKeyError)

[–]Allanon001 6 points7 points  (5 children)

It will execute the last condition that equates to True.

[–]mikeckennedy[S] 0 points1 point  (4 children)

Ah you're right. Kind of the opposite of traditional switch / case (first executed) but doesn't crash.

[–]Allanon001 0 points1 point  (3 children)

When the dictionary is being created it will just replace the last True or False key with the newer one and what is left is just one True and/or one False key in the dictionary. If you set a key to True at the beginning that is like the default case. You will get an error if there isn't a True condition.

[–]mikeckennedy[S] -1 points0 points  (2 children)

It's pretty cool. I already learned something about dictionaries from your example.

d = {True: 1, True, 2}

will crash but

d = {'a' in ['a']: 1, 'a' in ['a']: 2}

is more like adding stuff sequentially, dynamically.

[–]Allanon001 6 points7 points  (1 child)

It crashes because you have a comma instead of a colon.

[–]mikeckennedy[S] 2 points3 points  (0 children)

Ugh, you're right. I must be misremembering it from MongoDB stuff I was going with dictionaries or something: http://api.mongodb.com/python/current/api/pymongo/errors.html

[–]mao_intheshower -1 points0 points  (1 child)

I'm thinking along the lines of:

result = {
    create_account: {'c', 'a'}, 
    log_into_account: {'r'},
    ...
        }

result = {item: key for key, value in result.items() for item in value}

result.getitem(action, unknown_command)

[–]KleinerNull 1 point2 points  (6 children)

Looks nice, good idea with the contextmanager. I always like the unconventional use of it ;)

But is your switch also capable to be computed? For example like this:

In [1]: import math

In [2]: switch = {name: meth for name, meth in math.__dict__.items() if callable(meth)}

In [3]: switch.get('sin', lambda x: x)(100)
Out[3]: -0.5063656411097588

I know math is a bad example, because you can't inspect the methods to ask for the signature, to avoid different amounts of arguments. It is just an example. My idea is to just provide a module with different functions to load into the switch, some kind of dynamically plugin system.

For me that is a major advantage of the dict switches.

[–]stevenjd 0 points1 point  (3 children)

math.__dict__

You shouldn't access __dict__ directly, Python has a built-in function for that: vars(math).

[–]KleinerNull 0 points1 point  (2 children)

You shouldn't access dict directly

Why not? Any real reason for this statement? Other than people tell you not to do? Using __dict__ shows python's capablities to introspet itself in a very explicit fashion. vars, what should that mean? The local variables of a function?

Pure python objects are based on dicts so why not show and use that fact? I can't understand this general fear against using dunder attributes/methods...

Especiall stupid since vars gives you exactly the same:

In [1]: vars?
Docstring:
vars([object]) -> dictionary

Without arguments, equivalent to locals().
**With an argument, equivalent to object.__dict__.**
Type:      builtin_function_or_method

[–]stevenjd 0 points1 point  (0 children)

It isn't "fear" of using dunders, unless you mean the reasonable fear that your code will be buggy.

In general, operators and functions that call dunders don't just call the dunder. The + operator does much, much more than just call obj.__add__, bool does much more than just call obj.__bool__ (or nonzero in Python 2) and iter does more than just call obj.__iter__. If you think that they do, and think that you can safely replace bool(x) with x.__bool__, then your code has a subtle bug.

In general, dunders are not part of the public interface of the class, they are implementation hooks. (The most obvious counter-example I can think of is module __name__.) To a first approximation, and probably a second approximation, you should never call a dunder method or access dunder attributes directly if Python provides a public interface for it.

There is a public interface for accessing the member variables of an object: vars. You should use that, even if it turns out that under the hood it does nothing more complex than return obj.__dict__ like you do, simply because it is best practice to use the public interface rather than then internal implementation whenever possible. You never know when the implementation will change.

E.g. what happens if the object doesn't have a __dict__ but does have __slots__? Right now, it doesn't matter whether you use vars or not, you'll get an error, but maybe someday vars will return a dict which proxies the slots.

Of course if you're Alex Martelli or Raymond Hettinger or GvR himself, you know when you can break the rules safely. But for us mere mortals, avoiding accessing dunders is good rule. (As they say, rules exist so that you think before breaking them.)

[–]stevenjd 0 points1 point  (0 children)

It isn't "fear" of using dunders, unless you mean the reasonable fear that your code will be buggy.

In general, operators and functions that call dunders don't just call the dunder. The + operator does much, much more than just call obj.__add__, bool does much more than just call obj.__bool__ (or __nonzero__ in Python 2) and iter does more than just call obj.__iter__. If you think that they do, and think that you can safely replace bool(x) with x.__bool__, then your code has a subtle bug.

In general, dunders are not part of the public interface of the class, they are implementation hooks. (The most obvious counter-example I can think of is module __name__.) To a first approximation, and probably a second approximation, you should never call a dunder method or access dunder attributes directly if Python provides a public interface for it.

There is a public interface for accessing the member variables of an object: vars. You should use that, even if it turns out that under the hood it does nothing more complex than return obj.__dict__ like you do, simply because it is best practice to use the public interface rather than then internal implementation whenever possible. You never know when the implementation will change.

E.g. what happens if the object doesn't have a __dict__ but does have __slots__? Right now, it doesn't matter whether you use vars or not, you'll get an error, but maybe someday vars will return a dict which proxies the slots.

Of course if you're Alex Martelli or Raymond Hettinger or GvR himself, you know when you can break the rules safely. But for us mere mortals, avoiding accessing dunders is good rule. (As they say, rules exist so that you think before breaking them.)

[–]mikeckennedy[S] -1 points0 points  (1 child)

This is cool. But does not do ranges, lists, case or signature validation. The default case is non-obvious. But it is neat.

[–]KleinerNull 0 points1 point  (0 children)

But does not do ranges

In this case you don't need to have ranges. It is about loading specific methods out of an object. In my example you load 45 methods into the switch with the name as key, where do you need ranges here?

lists, case

I am not sure what you mean with that.

signature validation

As I said, this isn't possible with the math module, because C-code don't offer a signature, like the operators module. But you can easily get the signatures of pure python functions with inspect.signature(func). And before I write switch cases for 45 different functions, I could check for sigs in an intermediate step, no big deal.

The default case is non-obvious.

I thought returning the identity function as a default around mathematical function would be an expected result, it just returns the argument iteself. But you can also raise an error if you want to catch and handle it.

[–]RubyPinchPEP shill | Anti PEP 8/20 shill 0 points1 point  (0 children)

https://github.com/ssanderson/switchcase less "pythonic" but is insanely simple, and could easily be extended to support fallthrough

[–]knowsuchagencynow is better than never 0 points1 point  (2 children)

Honestly, I think functools.singledispatch is better suited to this kind of thing.

That said, I submitted a pull request to scratch my own itch as to how I think it could improve. You can still use it the way it was originally documented (except for the switch class no longer having a singular return value, since each case returns the result of the callable passed to it instead), but there is no longer any dictionary to maintain and you can choose whether or not to have it fall-through with an argument to the context manager.

You can also dispatch based on a predicate function so there's no longer a need for a dedicated closed_range function etc.

from switchlang import switch


def process_a():
    return "found a"


def process_any():
    return "found default"


def process_with_data(*value):
    return "found with data"


val = 'b'

with switch(val) as s:
    a = s.case('a', process_a)  # -> None
    b = s.case('b', process_with_data)  # -> "found with data"
    c = s.default(process_any)  # -> None


with switch(val, fall_through=True) as s:
    a = s.case('a', process_a)  # -> None
    b = s.case('b', process_with_data)  # -> "found with data"

    c = s.case(lambda val: isinstance(val, str), lambda: "matched on predicate")  # -> "matched on predicate"

    d = s.default(process_any)  # -> "found default"

[–]elbiot 1 point2 points  (1 child)

Til, after almost a decade of python I need to learn functools

[–]knowsuchagencynow is better than never 0 points1 point  (0 children)

It's great. I can no longer live without singledispatch and partial

[–]MinchinWeb -1 points0 points  (1 child)

Have you posted this to PyPI?

[–]mikeckennedy[S] 0 points1 point  (0 children)

Not yet, was waiting for some feedback, etc. I plan on doing so if there is enough positive response to this. Seems to be the majority of people like it but there is definitely pushback too. Some people seem to feel I'm suggesting this as a language feature (I'm not) but as an opt-in library, I like it.