Python 用字典模拟switch-case语句,Python没有switch/case
语句,因此有时候需要用很长的if...elif...else
链作为替代品。本节将介绍一个在Python中使用字典和头等函数来模拟switch/case
语句的技巧。听起来很激动人心,下面开始吧!
假设程序中有以下if
链:
>>> if cond == 'cond_a':
... handle_a()
... elif cond == 'cond_b':
... handle_b()
... else:
... handle_default()
当然,这里只有三种情况,还不算太糟。但如果有十几个elif
分支,那么就有点麻烦了。我认为非常长的if
语句链是一种糟糕的编码方式,让程序难以阅读和维护。
用字典查找表可以模拟switch/case
语句的行为,从而替换这种很长的if...elif...else
语句。
思路是利用Python中头等函数的特性,即函数可以作为参数传递给其他函数,也可作为其他函数的值返回,还可以分配给变量并存储在数据结构中。
例如,我们可以定义一个函数并存储在列表中以备后用:
>>> def myfunc(a, b):
... return a + b
...
>>> funcs = [myfunc]
>>> funcs[0]
<function myfunc at 0x107012230>
调用该函数的语法很直观,只需要在列表中使用索引访问,然后用()
调用语法来调用函数并传递参数:
>>> funcs[0](2, 3)
5
那么如何使用头等函数的特性简化链式if
语句呢?核心思想是定义一个字典,在字典的键值对中,键是输入条件,值是用来执行对应操作的函数:
>>> func_dict = {
... 'cond_a': handle_a,
... 'cond_b': handle_b
... }
这样就不必通过if
语句进行筛选,而是在检查每个条件时,查找对应的字典键来获取并调用相应的处理函数:
>>> cond = 'cond_a'
>>> func_dict[cond]()
这个实现差不多可以工作了,只要cond
位于字典中即可。如果不存在,则会得到KeyError
异常。
因此还需要用一种方法来支持默认情况以便对应if
语句中的else
分支。幸运的是Python字典有一个get()
方法,用来返回给定键的值;如果找不到,则返回一个默认值。这正好能用于此处:
>>> func_dict.get(cond, handle_default)()
这段代码可能乍一看语法很奇怪,但在深入剖析后,你会发现其工作方式与前面的例子完全相同。这里又用到了Python的头等函数特性,将handle_default
传递给get()
作为查找的备选值。此时如果在字典中找不到某个条件就会调用默认处理函数,不会再抛出KeyError
。
下面来看一个更完整的例子,其中使用了字典查找和头等函数替换if
语句链。你阅读完下面这个示例就能明白这种固定模式,它用来将某些类型的if
语句转换为基于字典的分支决策。
首先用if
链来编写一个函数,之后再转成字典形式。该函数接受像add
或mul
这样的字符串操作码,然后对操作数x
和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
说实话,虽然这只是另一个简单的示例(完整的例子需要粘贴大段无聊的代码),但能很好地说明底层的设计模式。一旦理解这种模式,就能将其应用于各种不同的场景。
你可以尝试调用dispatch_if()
函数,向其传递字符串操作码和两个数来执行简单的计算:
>>> dispatch_if('mul', 2, 8)
16
>>> dispatch_if('unknown', 2, 8)
None
注意,unknown
的情形能够正常工作是因为Python会向所有函数结尾添加隐式的return None
语句。
到现在为止还不错,现在将原始dispatch_if()
转换成一个新函数,其中使用字典将操作码映射到用于对应的头等函数中,以便执行相应的算术运算。
>>> 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)()
这种基于字典的实现结果与原始dispatch_if()
相同。两个函数的调用方式完全相同:
>>> dispatch_dict('mul', 2, 8)
16
>>> dispatch_dict('unknown', 2, 8)
None
这段代码还可在几个方面继续改进从而达到实用水准。
首先,每次调用dispatch_dict()
时都会为操作码查找创建一个临时字典和一串lambda表达式,从性能角度来看这并不理想。对于注重性能的代码,可以先创建字典并将其作为常量,之后调用该函数时可以再次引用这个字典,不用每次查找时都重新创建。
其次,如果真的想做一些像x + y
这样简单的算术,那么最好使用Python的内置operator
模块,而不是使用示例中的lambda函数。operator
模块实现了所有的Python操作符,例如operator.mul
和operator.div
等。不过这不是什么大问题。前面只是故意使用lambda来让例子更具普适性,有助于你将这种模式应用到其他情况。
现在你又学会了一种技巧,可以用来简化某些冗长的if
语句链。但要记住,这种技术不是万金油,有时用简单的if
语句会更好。
关键要点
-
Python没有
switch/case
语句,但在某些情况下可以使用基于字典的调度表来避免长if
语句链。 -
这个技巧再次证明了Python的头等函数是强大的工具,但能力越大,责任越大。