[Python-ideas] Yet Another Switch-Case Syntax Proposal

Anthony Towns aj at erisian.com.au
Sat Apr 19 13:12:47 CEST 2014


On 19 April 2014 13:12, Steven D'Aprano <steve at pearwood.info> wrote:
> One motive I'd like to see is, could a switch/case statement be used to
> automate table lookups? Two problems -- albeit mild ones -- with the
> table lookup idiom is that the table lives longer than needed, and it
> puts the table in the wrong place. This motivated Nick to suggest a
> "where" block:
>
> card = table[tarot] where:
>     table = {0: "Fool", 1: "Alan Moore", 2: "High Priestess", ...}
>
>
> solving both problems at once: the table is declared only when needed,
> not before hand, and does not exist outside of the block. A third
> problem, not solved by Nick's "where", is that the table requires every
> case's value ahead of time, whether it will be needed or not. When the
> values are constants, that's not a big deal, but they might be expensive
> expressions.

A related problem is that a table lookup can only hold expressions,
not statement blocks. If you want statement blocks you'd have to
define functions first, then reference back to them in the table, then
lookup the table. (And if you just want late/lazy evaluation but not
blocks, you end up having lambda expressions in your tables)

It might be reasonable to consider a "switch" statement to solve just
those problems; ie:

     case X in:
        1: print("Hello, world")
        2: print("Goodbye, world")
        3: print("Whatever")
        ...
        else: raise Exception("unexpected value for X")

would be equivalent to:

    __cases = {}
    def _():
        print("Hello, world")
    __cases[1] = _
    ...
    def _():
        raise Exception("unexpected value for X")
    __cases.default = _

    __cases.get(X, __cases.default)()

If you treated it literally that way, building a dict up from nothing
every time the "case" block was hit, it would just be syntactic sugar
for if/elif, which doesn't seem valuable. (It'd save you repeating the
"X =="  part of every condition, and you might get an error if you had
"1:" as two different cases, which could be useful)

if you had the __selector dict populated at parse time, you'd be able
to reuse it on each iteration/call to the function and could get a
performance win (you'd only evaluate the right hand side of the
expressions once, and you'd just be doing a hash lookup on each
iteration/invocation). The downside is that dynamic values in the
cases would then be confusing:

    y = 0
    def foo(x):
        case x in:
            y:
                return "matched y"
            else:
                return "didn't match y"

    foo(0) # matched y
    y = 1
    foo(1) # didn't match y

or:

    for i in range(10):
        case i in:
            i:
                print("i == i")
            else:
                print("i != i")

might only print i == i when i is 0 (or might give a syntax error that
i is undefined when first compiling the select block).

(The above would be Alternative 4, with case instead of switch as the
keyword, Option 3 from PEP 3103 I think)

C/C++ doesn't have that problem because it can just issue an error at
compile time that "i" isn't a constant expression. I don't think you
could do anything similar in python, because you couldn't distinguish
between symbolic constants and variables... You could argue that it's
similar to when you have mutable default arguments to functions and
expect people to just deal with it, but that doesn't seem ideal
either...

You could always just refactor your "switch" statement out of your
function body and into a separate definition, in which case you could
just define a class:

    def card_id(id):
        def dec(f):
            f = staticmethod(f)
            TarotActor_card_actions[id] = f
            return f
        return dec
    TarotActor_card_actions = {}

    class TarotActor:
        @classmethod
        def dispatch(cls, card_id):
            return TarotActor_card_actions[id]()

        @card_id(0)
        def Fool():
            ...
        @card_id(1)
        def Magician():
            ...

and then just invoke  TarotActor.dispatch(tarot_card)  from wherever.
Having to do two separate lines for the code block introduction
(decorator and def, versus just a case statement) is irritating I
suppose, but it's not that bad. And since it's three different levels
of indentation (switch statement, cases, and code blocks), moving it
to module level is arguably a good idea just for indentation's sake...

Cheers,
aj

-- 
Anthony Towns <aj at erisian.com.au>


More information about the Python-ideas mailing list