【问题标题】:Python if statement efficiencyPython if 语句效率
【发布时间】:2011-02-02 02:31:39
【问题描述】:

一位朋友(低技能水平的休闲 Python 脚本编写者)让我查看一些代码。我注意到他有 7 个单独的陈述,基本上都说了。

if ( a and b and c):
    do something

语句 a,b,c 都测试了它们是否相等或缺少设置值。当我查看它时,我发现由于测试的性质,我可以将整个逻辑块重写为 2 个分支,这些分支从未超过 3 个深度并且很少超过第一级(使最罕见的发生测试首先)。

if a:
    if b:
        if c:
    else:
        if c:
else:
    if b:
        if c:
    else:
        if c:

对我来说,从逻辑上讲,如果您进行更少、更简单的测试,这些测试会更快地失败并继续前进。 我真正的问题是

1) 当我说 if 和 else 时,如果 if 为真,那么 else 会被完全忽略吗?

2) 理论上会

如果(a和b和c)

花费与三个单独的 if 语句一样多的时间?

【问题讨论】:

    标签: python performance


    【解决方案1】:

    我会说单个测试与单独测试一样快。 Python 也使用所谓的short-circuit evaluation

    这意味着对于(a and b and c),如果afalse,则bc 将不再被测试。

    类似地,如果您有一个 OR 表达式 (a or b) 并且 atrue,则永远不会评估 b

    所以总而言之,分句不会更快地失败

    【讨论】:

    • 这比接受的答案更有帮助。它通过重新排序参数将我的执行速度提高了 20%,因为其中一个是字典查找发生的次数不平衡
    【解决方案2】:

    if 语句将跳过 else 括号中的所有内容,如果它评估为真。应该注意的是,担心这类问题,除非每次程序执行数百万次,否则称为“过早优化”,应该避免。如果您的代码使用三个 if (a and b and c) 语句更清晰,则应保留它们。

    【讨论】:

    • 同意。不要担心性能,担心可读性。正如 Martin Fowler 所说:“任何傻瓜都可以编写计算机可以理解的代码……但只有优秀的程序员才能编写人类可以理解的代码。”
    • 如果您怀疑自己有性能问题,首先要做的是测量代码的速度。然后编写你的替代版本,测量它,看看你的改变是否真的让代码更快。 Code Complete 中有一个很棒的部分介绍了这一点。
    • @DenilsonSáMaia 是否应该为了清楚起见而避免过早优化?这些语句对我来说似乎同样清楚,所以我认为使用分支语句提高性能在这里也不错。
    • 如果程序要迭代大约100万次怎么办,我应该如何提高IF ELSE语句的效率?谢谢
    【解决方案3】:

    代码:

    import dis
    
    def foo():
      if ( a and b and c):
        pass
      else:
        pass
    
    def bar():
      if a:
        if b:
          if c:
            pass
    
    print 'foo():'
    dis.dis(foo)
    print 'bar():'
    dis.dis(bar)
    

    输出:

    foo():
      4           0 LOAD_GLOBAL              0 (a)
                  3 JUMP_IF_FALSE           18 (to 24)
                  6 POP_TOP             
                  7 LOAD_GLOBAL              1 (b)
                 10 JUMP_IF_FALSE           11 (to 24)
                 13 POP_TOP             
                 14 LOAD_GLOBAL              2 (c)
                 17 JUMP_IF_FALSE            4 (to 24)
                 20 POP_TOP             
    
      5          21 JUMP_FORWARD             1 (to 25)
            >>   24 POP_TOP             
    
      7     >>   25 LOAD_CONST               0 (None)
                 28 RETURN_VALUE        
    bar():
     10           0 LOAD_GLOBAL              0 (a)
                  3 JUMP_IF_FALSE           26 (to 32)
                  6 POP_TOP             
    
     11           7 LOAD_GLOBAL              1 (b)
                 10 JUMP_IF_FALSE           15 (to 28)
                 13 POP_TOP             
    
     12          14 LOAD_GLOBAL              2 (c)
                 17 JUMP_IF_FALSE            4 (to 24)
                 20 POP_TOP             
    
     13          21 JUMP_ABSOLUTE           29
            >>   24 POP_TOP             
                 25 JUMP_ABSOLUTE           33
            >>   28 POP_TOP             
            >>   29 JUMP_FORWARD             1 (to 33)
            >>   32 POP_TOP             
            >>   33 LOAD_CONST               0 (None)
                 36 RETURN_VALUE        
    

    因此,尽管设置相同,但组合表达式的清理速度更快,因为它只在堆栈上留下一个值。

    【讨论】:

    • 不错的测试!在 python 3.8.3 (macos) 上运行它不再是这种情况,两个函数具有完全相同的输出
    【解决方案4】:

    至少在python中,效率仅次于可读性和“平面优于嵌套”。

    The Zen of Python

    【讨论】:

      【解决方案5】:

      如果您担心 b 或 c 是被调用的函数,而不仅仅是被评估的变量,那么这段代码表明短路是您的朋友:

      a = False
      def b():
          print "b was called"
          return True
      
      if a and b():
          print "this shouldn't happen"
      else:
          print "if b was not called, then short-circuiting works"
      

      打印

      if b was not called, then short-circuiting works
      

      但如果你有这样的代码:

      a = call_to_expensive_function_A()
      b = call_to_expensive_function_B()
      c = call_to_expensive_function_C()
      
      if a and b and c:
          do something...
      

      那么您的代码仍在调用所有 3 个昂贵的函数。最好让 Python 成为 Python:

      if (call_to_expensive_function_A() and
          call_to_expensive_function_B() and
          call_to_expensive_function_C())
          do something...
      

      它只会调用尽可能多的昂贵函数来确定整体状况。

      编辑

      您可以使用内置的all 进行概括:

      # note, this is a list of the functions themselves
      # the functions are *not* called when creating this list
      funcs = [function_A, function_B, function_C]
      
      if all(fn() for fn in funcs):
          do something
      

      现在,如果您必须添加其他功能,或者想要重新排序它们(也许function_A 非常耗时,您可以通过过滤失败的案例来受益 function_Bfunction_C 首先),您只需更新funcs 列表。 all 进行短路,就像您将 if 拼写为 if a and b and c 一样。 (如果函数被 'or'ed 在一起,请改用 any 内置函数。)

      【讨论】:

        【解决方案6】:

        我怀疑你会看到一个可衡量的差异,所以我建议你做任何使代码最易读的事情。

        【讨论】:

          【解决方案7】:

          如果a 是假的,if (a and b and c) 将失败,而无需检查bc

          也就是说,我个人觉得嵌套条件比 2^n 个条件组合更容易阅读。

          一般来说,如果您想确定哪种方式最快,您可以使用timeit 编写一个简单的基准测试。

          【讨论】:

            【解决方案8】:

            if (a and b and c)更快更好,为了“真正程序员的代码优化”和代码可读性。

            【讨论】:

              【解决方案9】:

              只说构造

              if a:
                if b:
                  if c:
                else:    #note A
                  if c:
              else:
                if b:
                  if c:
                else:    #note A
                  if c:
              

              构造不好。在 "(a and b and c)" 构造解释器(和大多数 lang 编译代码)简单地忽略错误值后面的条件,因此 "#note A" else 分支不应该存在。写作(a 和 b 和 c)是隐含的。

              【讨论】:

                猜你喜欢
                • 2012-05-14
                • 1970-01-01
                • 1970-01-01
                • 2022-11-10
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2018-08-04
                相关资源
                最近更新 更多