Using dictionaries rather than complex if-elif-else clauses

Lately, I’ve been using dictionaries as a dispatching mechanism. It seems especially elegant when I face some fairly elaborate switching logic.

For example, instead of:


if a == 1 and b == 1:
    log("everything worked!")
    commit()


elif a == 1 and b == 0:
    log("a good, b bad")
    report_that_b_failed()


else:
    log("a failed")
    report_that_a_failed()

Do this:


d = {
    (1, 1): commit,
    (1, 0): report_that_b_failed,
    (0, 0): report_that_a_failed
}


k = (a, b)
f = d[k]
f()

This approach is also really useful when you face the need to change logic based on runtime values. You can imagine that d might be built after parsing an XML file.

  • Gustavo Nascente

    I like it! Good pactice ! This is very usefull for me!
    thanks!

  • doc awesome

    makes me wish python supported multiline lambdas

  • Sam Kong

    What happens if a = 0, b = 1?
    The two you compared are not exact, right?

    That said, I like your approach.

  • Neil Gall

    Nice, but I’d shorten the call site to d[(a,b)](), especially if it were used often.

  • Jonathan Allen

    I can certainly see myself using this in .NET programming.

  • njharman

    ok, but use vars longer than one char and please do this instead of pointless temp vars.

    d[(a,b)]()

  • http://www.giuliopetrucci.it Giulio

    Sound cool… ;-)
    Anyway, I’ll add:

    if not f:
    default()
    else:
    f()

    just to emulate a default behavior, which should be the correct failover if d has not the given (a,b) among its key. Correct? (I’m a half-newbie, so I apologize if I wrote stupid things…)

  • Jason

    This might be a bit dense, but what happens in the second example when you hit upon a (0,1), because the else block from the first can possibly map to (0,0) and (0,1) in the second example (assuming that a and b can only be 0 or 1), or have you accounted for this fact and that (0,1) can’t be reached…

    Also you’d need to put a comment or two in there so anyone that maintains it isn’t going to hit their head against the desk after the first casual glance – they’d be very used to seeing ‘if’ conditional blocks, right?

    #In this dictionary the keys represent various states of a and b that we can encounter.
    d{…}

    #Check a and b, and if the key (a,b) is in d, call the function that maps to that key.
    f()

    Good idea to cut code bloat though!

  • http://beebo.org/ Michael S.

    Is there an elegant way of constructing a catch-all “else”? (The code above misses the (0, 1) case.)

  • Tomas

    What happens when a == 0 and b == 1? The logic in the two cases are not equivalent.

  • http://www.pixelbeat.org/ Pádraig Brady

    You will need all combinations in the dict, so this will quickly explode for increasing numbers of variables. You need (0,1): report_that_a_failed BTW
    Or I suppose you could remove (0,0) from the dict and then:

    f=d.get(k, report_that_a_failed)

  • Stephen

    Don’t forget to use dict.get instead of dict[] so you can provide a catch-all case

    k = (a, b)
    f = d.get(k, report_case_not_found)
    f()

  • wilhelmtell

    There’s a runtime complexity price. imo, there’s a readability price too.

  • James

    This is terrible practice IMO. What if a==0 and b==1? Then your examples are not equivalent: you have to add another key to the dict or check if the key is in the dict or add a try/except wrapper around your dict lookup or use a default dict. The fact that there is so many ways to do it should clue you in that it breaks from Python philosophy (where there should only be one way).

    You’ve also lost your log statements preceding the report statements since you are limited to a single statement unless you wrap all your actions into functions or terse lambdas.

    I also don’t like debugging problems in execution path with data this way because it seperates the control structure from its result which makes looking up potential problems a bit of a pain — I promise you will know what I mean if you use this extensively.

    I think these are the kinds of shorcuts we end up paying for in bugs.

  • http://bigdingus.com Mike McNally

    That’s sort-of a weak version of pattern matching dispatch as in Haskell or Erlang. In Erlang that’s basically the way you do everything, because it’s idiomatic and easy.

  • http://tplus1.com matt

    Mike, thanks for the comment! Yeah, ever since I read about common lisp multimethods and prolog/haskell/erlang pattern matching, I’ve been craving those features when I can’t have them.

    Anyhow, this approach does allow easy programmatic manipulation, which I don’t know how to do in erlang. In other words, based on some inputs, I can rearrange the dictionary so keys point to different functions. I don’t know how I would do that in prolog.

  • http://www.codesoftly.aaronoliver.com Aaron

    This sort of thing gives me a pang of longing for switch/case syntax in Python. I know dictionaries, etc, can accomplish the same thing, but I’ve already trained my brain for case/switch stuff. Doing without it has always been a little awkward.

  • http://tplus1.com matt

    Hi Aaron — dictionaries are better than case-switch statements because the dictionary can be built based on some other data. For example, I could construct a dictionary from database information, or user inputs, the position of the stars, etc.

    A case-switch statement is static. Once it is written, it can’t be rewritten programmatically.

    Thanks for the comment!

  • Johan

    No one has mentioned this yet, but what you have done above is called a dispatch table and is a pretty common computer science concept. See the Wikipedia article for more examples (I can’t seem to post the link to it).

  • http://url.ie/h1b web design

    Pattern matching would be better. Is there a nice way to do pattern matching in Python?

  • http://www.codesoftly.aaronoliver.com Aaron

    @Matt & Johan,

    I get the whole dispatch thing, and I agree that it’s darn handy to have. But I ALSO want the rinky-dink switch/case syntax for simple cases. That way I can keep things readable when the conditionals are fairly static and simple.

    If I end up needing more sophisticated dispatch stuff, then I bust out my dictionary to do heavy lifting, sacrificing readability for power.

  • PJ

    I do this all the time. Also, don’t forget that variable names and inline literals can clarify things sometimes. Consider:

    case=(a,b)
    action = {
    (1, 1): commit,
    (1, 0): report_that_b_failed,
    (0, 0): report_that_a_failed
    }[case]
    action()

    I often do this for subcommand dispatch in commandline programs; a side effect is an easy way to list the possible options :

    Commands = { ‘folders’: folders,
    ‘folder’: folder,
    ‘debug’: debug,
    ‘pick’: pick,
    ‘refile’: refile,
    }

    cmdfunc = Commands.get(cmd,None)
    if cmdfunc:
    try:
    cmdfunc(cmdargs)
    except UsageError:
    print cmdfunc.__doc__
    sys.exit(1)
    config.write()
    state.write()
    else:
    print “Unknown command %s. Valid ones: %s ” % (sys.argv[1], ‘, ‘.join(_sort(Commands.keys())))

    The above is a snippet of real code from a commandline mailreader I wrote and use.

  • PersonWeir

    You got a nice blog up there.

    Thanks
    james kails
    ______________________________________________
    <a href=”http://www.SMARTSOURCENEWS.com ” target=”_blank”>pass a drug test | <a href=”http://www.PASS-ALL-DRUG-TEST.com ” target=”_blank”>Pass a THC drug test | <a href=”http://www.PASSDRUGTESTKIT.com ” target=”_blank”>pass marijuana drug test

  • PutmanRomero276

    i have posted your blog on my site

    Have a nice day
    james orel
    ______________________________________________
    watch one tree hill | watch lost | watch west wing

  • http://www.jts-landscaping.com/ Lanscaping Raleigh

    this is nice information need to know more

    respect
    sarshel niki
    ______________________________________________

  • Josh Grigonis

    Is (0, 1) just assumed to be impossible?

  • http://blog.tplus1.com Matt Wilson

    Great question.  In my code, that combo might raise a KeyError.  I suppose I could use .get() and If don't have a callable defined for that key, then I could raise a more particular error.

  • Tetha Z

    this made me smile, it's beautiful.

  • http://blog.tplus1.com Matt Wilson

    Thanks! Your comment made my day :)