【问题标题】:What is the best approach in python: multiple OR or IN in if statement?python中最好的方法是什么:if语句中的多个OR或IN?
【发布时间】:2013-07-12 12:33:08
【问题描述】:

python 中最好的方法是什么:if 语句中的 multiple ORIN?考虑性能和最佳实践。

if cond == '1' or cond == '2' or cond == '3' or cond == '4': pass

if cond in ['1','2','3','4']: pass

【问题讨论】:

    标签: python performance python-2.7 design-patterns


    【解决方案1】:

    最好的方法是使用set

    if cond in {'1','2','3','4'}:
    

    因为集合中的成员资格测试是 O(1)(恒定成本)。

    其他两种方法的复杂性相同;只是不变成本的差异。列表上的in 测试和or 链短路;一旦找到匹配项就终止。一个使用一系列字节码跳转(如果True 则跳转到末尾),另一个使用C 循环,如果值匹配则提前退出。在最坏的情况下,cond 确实 匹配序列中的元素,这两种方法都必须在返回False 之前检查所有 元素。在这两者中,我会随时选择in 测试,因为它更具可读性。

    【讨论】:

    • @JonathanSimonPrates:对旧版本使用 frozenset(['1', '2', '3', '4'])
    • Python 2.7 没有设置常量,因此in tuple 是一小组值的最佳选择。
    【解决方案2】:

    这实际上取决于 Python 的版本。在 Python 2.7 中,字节码中没有设置常量,因此在 Python 2 中,对于固定常量的小值集,使用元组:

    if x in ('2', '3', '5', '7'):
        ...
    

    元组是一个常数:

    >>> dis.dis(lambda: item in ('1','2','3','4'))
      1           0 LOAD_GLOBAL              0 (item)
                  3 LOAD_CONST               5 (('1', '2', '3', '4'))
                  6 COMPARE_OP               6 (in)
                  9 RETURN_VALUE
    

    Python 也足够聪明,可以将 Python 2.7 上的常量列表优化为元组:

    >>> dis.dis(lambda: item in ['1','2','3','4'])
      1           0 LOAD_GLOBAL              0 (item)
                  3 LOAD_CONST               5 (('1', '2', '3', '4'))
                  6 COMPARE_OP               6 (in)
                  9 RETURN_VALUE        
    

    但是 Python 2.7 字节码(和编译器)缺乏对常量集的支持:

    >>> dis.dis(lambda: item in {'1','2','3','4'})
      1           0 LOAD_GLOBAL              0 (item)
                  3 LOAD_CONST               1 ('1')
                  6 LOAD_CONST               2 ('2')
                  9 LOAD_CONST               3 ('3')
                 12 LOAD_CONST               4 ('4')
                 15 BUILD_SET                4
                 18 COMPARE_OP               6 (in)
                 21 RETURN_VALUE        
    

    这意味着if条件中的集合需要为每个测试重建。


    但是在 Python 3.4 中,字节码支持设置常量;那里的代码评估为:

    >>> dis.dis(lambda: item in {'1','2','3','4'})
      1           0 LOAD_GLOBAL              0 (item)
                  3 LOAD_CONST               5 (frozenset({'4', '2', '1', '3'}))
                  6 COMPARE_OP               6 (in)
                  9 RETURN_VALUE
    

    至于多or 代码,它会产生完全可怕的字节码:

    >>> dis.dis(lambda: item == '1' or item == '2' or item == '3' or item == '4')
      1           0 LOAD_GLOBAL              0 (item)
                  3 LOAD_CONST               1 ('1')
                  6 COMPARE_OP               2 (==)
                  9 JUMP_IF_TRUE_OR_POP     45
                 12 LOAD_GLOBAL              0 (item)
                 15 LOAD_CONST               2 ('2')
                 18 COMPARE_OP               2 (==)
                 21 JUMP_IF_TRUE_OR_POP     45
                 24 LOAD_GLOBAL              0 (item)
                 27 LOAD_CONST               3 ('3')
                 30 COMPARE_OP               2 (==)
                 33 JUMP_IF_TRUE_OR_POP     45
                 36 LOAD_GLOBAL              0 (item)
                 39 LOAD_CONST               4 ('4')
                 42 COMPARE_OP               2 (==)
            >>   45 RETURN_VALUE        
    

    【讨论】:

    • 使用Python3.9.9,frozenset比较是最快的。 >>> timeit.timeit("x in (1,2,3,4)", setup="x=3") 0.11902332499994372 >>> timeit.timeit("x in {1,2,3,4}", setup="x=3") 0.07512418700025592
    【解决方案3】:

    在大多数情况下,Pieters 的答案是最好的。但是,在您的具体情况下,我不会使用 inor 而是这样做:

    if 0 < int(cond) < 5:
    

    如果 cond 为“1”、“2”、“3”或“4”,则 if 块将运行。这样做的好处是它比其他答案短。

    【讨论】:

    • 如果 cond 保证为一个字符长,则 int 可能会被删除 '0' 也会起作用
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-13
    • 2012-08-20
    • 2011-01-10
    • 2014-07-21
    • 1970-01-01
    • 2015-05-16
    相关资源
    最近更新 更多