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
6561 次点击
所在节点    Python
54 条回复
cocofe0
2018-10-14 12:58:17 +08:00
我觉得用 dict 进行 case 和 func 的管理,最大的不便就是每次添加 case 都需要手动维护 dict,手动维护的都可能出现问题,而用装饰器能将维护 dict 自动化,这是最大的优点,其次,代码也更加简洁,并不觉得会特别难理解,(如果 dict 会频繁更新,我觉得这样做还是很有必要的)
bucky
2018-10-14 13:03:26 +08:00
@cocofe0 那直接把字典包装一下不就行了
littleshy
2018-10-14 13:07:39 +08:00
Simple is better than complex.
e9e499d78f
2018-10-14 13:11:45 +08:00
太 pythonic 了
zzj0311
2018-10-14 13:31:10 +08:00
为什么 Python 没有 switch case,因为没有必要~
newtype0092
2018-10-14 14:47:34 +08:00
你那句话化简一下就是:
“由于 Python 中没有。。。显得很不 Pythonic ”。
所以说 Python 的特性不 Pythonic ?好矛盾的一门语言。。。
megachweng
2018-10-14 15:59:56 +08:00
多了一种思路吧
Raisu
2018-10-14 17:14:35 +08:00
字典
neoblackcap
2018-10-14 17:28:24 +08:00
上面说了那么多,其实就是量小的时候用 if-else if-else 完全没有问题。
至于字典行不行?当然是行的啊,用字典属于表驱动模式的一种实现,完全是合乎软件工程的要求的。
lihongjie0209
2018-10-14 17:35:26 +08:00
可读性直线下降
BingoXuan
2018-10-14 18:25:00 +08:00
我们家 tinyrpc 框架就是这样实现的,管理大量函数调用时候很方便。但 team leader 就非常喜欢手动分拆多个还用字典再手动管理,简直蛋疼。
PythonAnswer
2018-10-14 19:37:54 +08:00
10 个以内 手写 if
10 个以外 手写字典
怎么简单怎么来啊
laqow
2018-10-14 19:47:51 +08:00
可读性和性能都下降
mseasons
2018-10-14 20:04:06 +08:00
代码量 UPUP
GreatTony
2018-10-14 21:01:15 +08:00
在这里总结一下,我博客里的内容也更新了,在文章最上面也有地址:

对比两种处理方案,区别在于显式*dict*的存在。对于显式的 dict 存在,方便索引和查看具体 case 和对应的处理函数,而对于 case 的增加或者删除,都得增加或删除对应主入口中 case 和 func 的键值对。而装饰器的存在简化了上述步骤,而对应的代价则是将 dict 的存在隐式化了,类似的设计模式同 Web 框架中路由注册。

1. specificdispatch 只是一个单纯的 functool,import 了就能用的那种,从行数上来说,使用装饰器和字典来说基本是没有差别的。
2. 从性能角度来说,查表的方法(字典和装饰器)的性能都是是比 `if` `elif` 要高的,是 O(1)的性能。
3. 字典和装饰器的方法,唯一的区别也是在字典是否显式存在,以及是否需要手动维护。
luguhu
2018-10-14 23:48:35 +08:00
嗯,明白了。这样确实更好维护,符合开放封闭原则。不过只限定 int 和 str 是不是不太好, 毕竟不只这两个可以做 key。以及 参数限定一个 是不是不太够。
caoz
2018-10-15 00:27:23 +08:00
"而装饰器的存在简化了上述步骤,而对应的代价则是将 dict 的存在隐式化了,类似的设计模式同 Web 框架中路由注册"

你是指 Flask 中的 route() 吗?个人感觉这种写法用不好很容易造成混乱,完全不如集中写在一块清晰明了,如: https://docs.djangoproject.com/en/2.1/topics/http/urls/#example
https://www.tornadoweb.org/en/stable/guide/structure.html#the-application-object
20015jjw
2018-10-15 00:33:46 +08:00
感觉瞎折腾
TJT
2018-10-15 01:45:13 +08:00
书读的太少, 瞎折腾, 不过思路不错, 只是不适合而已.

@GreatTony 性能角度上来说, 量少的话 if else 是比较快的. 另外 Python dict 内存效率并不高.

@caoz 你想的话, 也可以写一块: http://flask.pocoo.org/docs/1.0/api/#flask.Flask.add_url_rule
deepreader
2018-10-15 02:46:12 +08:00
我觉得想法不错,而且省了很多 if-else statement.

有个疑问,这个需要 case key 能 hashable,万一我的 if 的条件判断很复杂怎么办?判断条件并不是简单地 case == 1 etc.

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

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

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

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

© 2021 V2EX