Python 用字典模拟switch-case语句

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链来编写一个函数,之后再转成字典形式。该函数接受像addmul这样的字符串操作码,然后对操作数xy进行一些运算:

>>> 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.muloperator.div等。不过这不是什么大问题。前面只是故意使用lambda来让例子更具普适性,有助于你将这种模式应用到其他情况。
现在你又学会了一种技巧,可以用来简化某些冗长的if语句链。但要记住,这种技术不是万金油,有时用简单的if语句会更好。

关键要点

  • Python没有switch/case语句,但在某些情况下可以使用基于字典的调度表来避免长if语句链。

  • 这个技巧再次证明了Python的头等函数是强大的工具,但能力越大,责任越大。

Python教程

Java教程

Web教程

数据库教程

图形图像教程

大数据教程

开发工具教程

计算机教程