Python Tricks - Dictionary Tricks(2)

Emulating Switch/Case Statements With Dicts

用字典仿制一个switch+case语句的写法
Python doesn’t have switch/case statements so it’s sometimes necessary to write long if...elif...else chains as a workaround. In this chapter you’ll discover a trick you can use to emulate switch/case statements in Python with dictionaries and first-class functions. Sound exciting? Great—here we go!

Imagine we had the following if-chain in our program:

>>> if cond == 'cond_a': 
...   handle_a()
... elif cond == 'cond_b': 
...   handle_b()
... else:
...   handle_default()

Of course, with only three different conditions, this isn’t too horrible yet. But just imagine if we had ten or more elif branches in this statement. Things would start to look a little different. I consider long if- chains to be a code smell that makes programs more difficult to read and maintain.

One way to deal with long if...elif...else statements is to replace them with dictionary lookup tables that emulate the behavior of switch/case statements.

The idea here is to leverage the fact that Python has first-class functions. This means they can be passed as arguments to other functions, returned as values from other functions, and assigned to variables and stored in data structures.
函数作为第一公民可以被传递到其他的函数,从其他函数中返回值,并将值赋给变量,存储在数据结构中。

For example, we can define a function and then store it in a list for later access:

>>> def myfunc(a, b):
...   return a + b
...
>>> funcs = [myfunc]
>>> funcs[0]
<function myfunc at 0x107012230>

The syntax for calling this function works as you’d intuitively expect— we simply use an index into the list and then use the “()” call syntax for calling the function and passing arguments to it:

 >>> funcs[0](2, 3) 5

Now, how are we going to use first-class functions to cut our chained if-statement back to size? The core idea here is to define a dictionary that maps lookup keys for the input conditions to functions that will carry out the intended operations:

>>> func_dict = {
...     'cond_a': handle_a,
...     'cond_b': handle_b
... }

这确实是一个很好的思路。

Instead of filtering through the if-statement, checking each condition as we go along, we can do a dictionary key lookup to get the handler function and then call it:

>>> cond = 'cond_a' 
>>> func_dict[cond]()

This implementation already sort-of works, at least as long as cond can be found in the dictionary. If it’s not in there, we’ll get a KeyError exception.

So let’s look for a way to support a default case that would match the original else branch. Luckily all Python dicts have a get() method on them that returns the value for a given key, or a default value if the key can’t be found. This is exactly what we need here:

>>> func_dict.get(cond, handle_default)()

This code snippet might look syntactically odd at first, but when you break it down, it works exactly like the earlier example. Again, we’re using Python’s first-class functions to pass handle_default to the get()-lookup as a fallback value. That way, if the condition can’t be found in the dictionary, we avoid raising a KeyError and call the default handler function instead.

Let’s take a look at a more complete example for using dictionary lookups and first-class functions to replace if-chains. After reading through the following example, you’ll be able to see the pattern needed to transform certain kinds of if-statements to a dictionary-based dispatch.

We’re going to write another function with an if-chain that we’ll then transform. The function takes a string opcode like "add" or "mul" and then does some math on the operands x and y:

>>> def dispatch_if(operator, x, y):
      if operator == 'add': 
        return x + y
      elif operator == 'sub': 
        return x - y
      elif operator == 'mul': 
        return x * y
      elif operator == 'div': 
        return x / y

To be honest, this is yet another toy example (I don’t want to bore you with pages and pages of code here), but it’ll serve well to illustrate the underlying design pattern. Once you “get” the pattern, you’ll be able to apply it in all kinds of different scenarios.

You can try out this dispatch_if() function to perform simple calculations by calling the function with a string opcode and two numeric operands:

>>> dispatch_if('mul', 2, 8)
16
>>> dispatch_if('unknown', 2, 8) 
None

Please note that the 'unknown' case works because Python adds an implicit return None statement to the end of any function.

So far so good. Let’s transform the original dispatch_if() into a new function which uses a dictionary to map opcodes to arithmetic operations with first-class functions.

>>> def dispatch_dict(operator, x, y):
      return {
        'add': lambda: x + y, 
        'sub': lambda: x - y, 
        'mul': lambda: x * y, 
        'div': lambda: x / y,
}.get(operator, lambda: None)()

上面写得确实很美

This dictionary-based implementation gives the same results as the original dispatch_if(). We can call both functions in exactly the same way:

>>> dispatch_dict('mul', 2, 8)
16
>>> dispatch_dict('unknown', 2, 8) 
None

There are a couple of ways this code could be further improved if it was real “production-grade” code.
但是还是有不少可以改进的地方
First of all, every time we call dispatch_dict(), it creates a temporary dictionary and a bunch of lambdas for the opcode lookup. This isn’t ideal from a performance perspective. For code that needs to be fast, it makes more sense to create the dictionary once as a constant and then to reference it when the function is called. We don’t want to recreate the dictionary every time we need to do a lookup.
每次我们调用dispatch函数的时候我们都是创建了临时的字典和一系列的lambda表达式,从性能方面看不是理想的。我们不如把字典创建为一个定量值,然后每一次使用的时候就去引用一下。

Second, if we really wanted to do some simple arithmetic like x + y, then we’d be better off using Python’s built-in operator module instead of the lambda functions used in the example. The operator module provides implementations for all of Python’s operators, for example operator.mul, operator.div, and so on. This is a minor point, though. I intentionally used lambdas in this example to make it more generic. This should help you apply the pattern in other situations as well.
我们如果需要使用x + y 这样的表达式我们最好使用内置库里面的一些操作方法,比如operator中的add mul div等等方法。

Well, now you’ve got another tool in your bag of tricks that you can use to simplify some of your if-chains should they get unwieldy. Just remember—this technique won’t apply in every situation and some- times you’ll be better off with a plain if-statement.

Key Takeaways
  • Python doesn’t have a switch/case statement. But in some cases you can avoid long if-chains with a dictionary-based dispatch table.
  • Once again Python’s first-class functions prove to be a powerful tool. But with great power comes great responsibility.
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,744评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,505评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,105评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,242评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,269评论 6 389
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,215评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,096评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,939评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,354评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,573评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,745评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,448评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,048评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,683评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,838评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,776评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,652评论 2 354

推荐阅读更多精彩内容