【问题标题】:Python if vs try-exceptPython if vs try-except
【发布时间】:2011-04-25 04:42:57
【问题描述】:

我想知道为什么 try-except 比下面程序中的 if 慢。

def tryway():
    try:
        while True:
            alist.pop()
    except IndexError:
        pass

def ifway():
    while True:
        if alist == []: 
            break
        else:
            alist.pop()
if __name__=='__main__':
    from timeit import Timer
    alist = range(1000)
    print "Testing Try"
    tr = Timer("tryway()","from __main__ import tryway")
    print tr.timeit()
    print "Testing If"
    ir = Timer("ifway()","from __main__ import ifway")
    print ir.timeit()

我得到的结果很有趣。

Testing Try
2.91111302376
Testing If
0.30621099472

谁能解释一下为什么尝试这么慢?

【问题讨论】:

  • 这里并不真正适用,但有时查看dis.dis(funcname) 可以方便地了解某些东西在内部是​​如何工作的

标签: python performance


【解决方案1】:

您只设置了一次 alist。第一次调用“tryway”会清除它,然后每次后续调用什么都不做。

def tryway():
    alist = range(1000)
    try:
        while True:
            alist.pop()
    except IndexError:
        pass

def ifway():
    alist = range(1000)
    while True:
        if alist == []:
            break
        else:
            alist.pop()
if __name__=='__main__':
    from timeit import Timer
    print "Testing Try"
    tr = Timer("tryway()","from __main__ import tryway")
    print tr.timeit(10000)
    print "Testing If"
    ir = Timer("ifway()","from __main__ import ifway")
    print ir.timeit(10000)

>>> Testing Try
>>> 2.09539294243
>>> Testing If
>>> 2.84440898895

【讨论】:

  • James,如果我们没有什么可学的,而且从不犯愚蠢的错误,那么 StackOverflow 就没有理由了。
【解决方案2】:

在大多数语言中,异常处理通常很慢。大多数编译器、解释器和 VM(支持异常处理)将异常(语言习语)视为异常(不常见)。性能优化涉及权衡取舍,快速生成异常通常意味着语言的其他领域会受到影响(无论是性能还是设计的简单性)。

在更技术层面上,异常通常意味着 VM/解释器(或运行时执行库)必须保存一堆状态并开始提取函数调用堆栈上的所有状态(称为展开),直到找到有效捕获(除外)的点。

或者从不同的角度来看,当发生异常并由“调试器”接管时,程序会停止运行。此调试器在堆栈(调用函数数据)中搜索与异常匹配的捕获。如果它找到了,它会清理并在此时将控制权返回给程序。如果找不到,则将控制权返回给用户(可能以交互式调试器或 python REPL 的形式)。

【讨论】:

  • 这在 python 的上下文中是相当离题的。 python中的异常非常便宜,如果非异常情况比异常更常见(应该如此),那么尝试/捕获比检查更便宜 - 正如格伦的回答所证实的那样。 EAFP > LBYL。
【解决方案3】:

如果你真的对速度感兴趣,你的两个参赛者都可以减肥。

while True:while 1: 慢 -- True 是一个全局“变量”,已加载和测试; 1 是一个常量,编译器会进行测试并发出无条件跳转。

while True: 在 ifway 中是多余的。将 while/if/break 折叠在一起:while alist != []:

while alist != []: 是一种慢写方式while alist:

试试这个:

def tryway2():
    alist = range(1000)
    try:
        while 1:
            alist.pop()
    except IndexError:
        pass

def ifway2():
    alist = range(1000)
    while alist:
        alist.pop()

`

【讨论】:

  • 虽然您对 True 的评论在 py 2.x 的上下文中是正确的,但在 py3k 中它是一个真正的关键字并且没有这种开销。
【解决方案4】:

虽然有时我们希望列表在物理上缩小以便我们知道还剩下多少,但使用 for 进行迭代仍有更快的方法。然后 alist 应该是生成器的参数。 (John 也适合while alist:)我将函数作为生成器并使用 list(ifway()) 等,因此这些值实际上是在函数外使用(甚至未使用):

def tryway():
    alist = range(1000)
    try:
        while True:
            yield alist.pop()
    except IndexError:
        pass

def whileway():
    alist = range(1000)
    while alist:
         yield alist.pop()

def forway():
    alist = range(1000)
    for item in alist:
         yield item

if __name__=='__main__':
    from timeit import Timer
    print "Testing Try"
    tr = Timer("list(tryway())","from __main__ import tryway")
    print tr.timeit(10000)
    print "Testing while"
    ir = Timer("list(whileway())","from __main__ import whileway")
    print ir.timeit(10000)
    print "Testing for"
    ir = Timer("list(forway())","from __main__ import forway")
    print ir.timeit(10000)

J:\test>speedtest4.py
Testing Try
6.52174983133
Testing while
5.08004508953
Testing for
2.14167694497

【讨论】:

  • 虽然您的结果显示 trywhile 慢一点,但我对 Python 3 的测试给了我以下信息:Testing Try 1.0719404926951281 Testing while 1.2370897208711098 Testing for 0.5321000439965737(我只是在打印语句中添加了括号和将range(1000) 更改为list(range(1000))。)
【解决方案5】:

不确定,但我认为是这样的:while true 遵循正常的指令行,这意味着处理器可以流水线并做各种好事。异常会直接跳过所有这些,因此 VM 需要对其进行特殊处理,这需要时间。

【讨论】:

  • 我看待它的方式是,如果当它杀死自己的异常时,它只是在最后检查然后结束。而 if 外观正在测试列表 1000 次,我认为这会更慢....
  • 我认为与空列表的比较相当快,因此不会浪费很多时间。请注意,我不确定这些事情,只是想我会给你我的想法。
【解决方案6】:

只是想把这个混在一起: 我尝试了以下脚本,这似乎表明处理异常比处理else 语句要慢:

import time

n = 10000000
l = range(0, n)
t0 = time.time()
for i in l:
    try:
        i[0]
    except:
        pass
t1 = time.time()
for i in l:
    if type(i) == list():
        print(i)
    else:
        pass
t2 = time.time()
print(t1-t0)
print(t2-t1)

给予:

5.5908801555633545
3.512694835662842

因此,(尽管我知道有人可能会评论使用 time 而不是 timeit),但在循环中使用 try/except 似乎会减慢约 60%。因此,在经历数十亿个项目的 for 循环时,使用 if/else 可能会更好。

【讨论】:

    【解决方案7】:

    防御性编程要求对罕见和/或异常的情况进行一次测试,其中一些在一年或多年的过程中不会发生,因此在这些情况下,try-except 可能是合理的。

    【讨论】:

    • 这是一个非常令人困惑和不清楚的答案。请澄清。
    • 我相信他的意思是例外的字面意思。假设在 API 中,您希望 POST 数据始终包含一个 int,可以是字符串文字,也可以是一个 int。所以你天真地检查:if int(data["value"])。然后在美好的一天,您开始收到undefined。使用try,您可以使用ValueError
    猜你喜欢
    • 2016-08-30
    • 2021-03-15
    • 2019-06-26
    • 2014-01-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-22
    相关资源
    最近更新 更多