Python Switch Case 最佳实践

2018-10-14 08:32:47 +08:00
 GreatTony

优美胜于丑陋 import this

博客地址:Specific-Dispatch

前言

表驱动法是一种编辑模式( Scheme )——从表里面查找信息而不使用逻辑语句(ifcase)。事实上,凡是能通过逻辑语句来选择的事物,都可以通过查表来选择。

对简单的情况而言,使用逻辑语句更为容易和直白。但随着逻辑链的越来越复杂,查表法也就愈发显得更具吸引力。

Python 的switch case

由于 Python 中没有switch case关键词,所以对于每一种情况的逻辑语句只能用if,elif,else来实现,显得很不 Pythonic.

def handle_case(case):
    if case == 1:
        print('case 1')
    elif case == 2:
        print('case 2')
    else:
        print('default case')

而受到PEP-443: Single-dispatch generic functions的启发,很容易就能实现如下装饰器:

from functools import update_wrapper
from types import MappingProxyType
from typing import Hashable, Callable, Union


def specificdispatch(key: Union[int, str] = 0) -> Callable:
    """specific-dispatch generic function decorator.

    Transforms a function into a generic function, which can have different
    behaviours depending upon the value of its key of arguments or key of keyword arguments.
    The decorated function acts as the default implementation, and additional
    implementations can be registered using the register() attribute of the
    generic function.
    """

    def decorate(func: Callable) -> Callable:
        registry = {}

        def dispatch(key: Hashable) -> Callable:
            """
            Runs the dispatch algorithm to return the best available implementation
            for the given *key* registered on *generic_func*.
            """
            try:
                impl = registry[key]
            except KeyError:
                impl = registry[object]
            return impl

        def register(key: Hashable, func: Callable=None) -> Callable:
            """
            Registers a new implementation for the given *key* on a *generic_func*.
            """
            if func is None:
                return lambda f: register(key, f)

            registry[key] = func
            return func

        def wrapper_index(*args, **kw):
            return dispatch(args[key])(*args, **kw)

        def wrapper_keyword(*args, **kw):
            return dispatch(kw[key])(*args, **kw)

        registry[object] = func
        if isinstance(key, int):
            wrapper = wrapper_index
        elif isinstance(key, str):
            wrapper = wrapper_keyword
        else:
            raise KeyError('The key must be int or str')
        wrapper.register = register
        wrapper.dispatch = dispatch
        wrapper.registry = MappingProxyType(registry)
        update_wrapper(wrapper, func)

        return wrapper

    return decorate

而之前的代码就能很优美的重构成这样:

@specificdispatch(key=0)
def handle_case(case):
    print('default case')

@handle_case.register(1)
def _(case):
    print('case 1')

@handle_case.register(2)
def _(case):
    print('case 2')

handle_case(1) # case 1
handle_case(0) # default case

而对于这样的架构,即易于扩展也利于维护。

更多实例

class Test:
    @specificdispatch(key=1)
    def test_dispatch(self, message, *args, **kw):
        print(f'default: {message} args:{args} kw:{kw}')

    @test_dispatch.register('test')
    def _(self, message, *args, **kw):
        print(f'test: {message} args:{args} kw:{kw}')

test = Test()
# default: default args:(1,) kw:{'test': True}
test.test_dispatch('default', 1, test=True)
# test: test args:(1,) kw:{'test': True}
test.test_dispatch('test', 1, test=True)

@specificdispatch(key='case')
def handle_case(case):
    print('default case')

@handle_case.register(1)
def _(case):
    print('case 1')

@handle_case.register(2)
def _(case):
    print('case 2')

handle_case(case=1)  # case 1
handle_case(case=0)  # default case
6402 次点击
所在节点    Python
54 条回复
deepreader
2018-10-15 02:46:40 +08:00
@20015jjw 老板又见你了
20015jjw
2018-10-15 02:52:23 +08:00
@deepreader 羡慕大佬一波点评
zhzer
2018-10-15 04:30:00 +08:00
这也很不 Pythonic 吧...
ackfin01
2018-10-15 08:16:16 +08:00
是一种方法,怎敢叫最佳实践。。2333
araraloren
2018-10-15 08:48:59 +08:00
我也觉得 python 没有 switch case 很不 Pythonic (逃
GreatTony
2018-10-15 09:14:26 +08:00
@luguhu key 这个参数是标注着 func 中需要识别参数的位置或名称的,判断条件是任何可 hash 的,注释还是都写清楚了的
GreatTony
2018-10-15 09:17:19 +08:00
@TJT https://docs.python.org/3.6/whatsnew/3.6.html#new-dict-implementation Python3.5+之后已经大幅度优化了 dict 的存储模型,基本的模式以及对应的算法以及算是最优的了。然后我前言里就说了,逻辑链少的时候用 if elif 完全没问题。
GreatTony
2018-10-15 09:25:31 +08:00
@deepreader singledispatch 的初衷是提供一种 Python 的函数重载机制的实现,我这个也差不多。如果你条件判断比较复杂的话,是不推荐是用隐式的判断设计的,那才是真的雪上加霜,尽管是可以实现的。
catsoul
2018-10-15 09:56:57 +08:00
我个人比较赞成 LZ 的方案,当你需要加入字典的方法分布在项目中多个不同源文件的情况,这种方式效率和错误率都大大降低。我个人的理念是:能让程序干的事情,为啥要手动。
catsoul
2018-10-15 09:58:51 +08:00
@catsoul 效率提高,少打了俩字儿
wutiantong
2018-10-15 10:47:56 +08:00
感觉楼主的路子走歪了,这样下去眼看要走火入魔啦
troywinter
2018-10-15 15:11:16 +08:00
书读的少,歪门邪道
pythonee
2018-10-18 21:24:21 +08:00
挺 pythonic 的呀
clamshine
2018-11-13 10:31:16 +08:00
受教 多谢

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/497441

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX