【问题标题】:Python FAQ: “How fast are exceptions?”Python 常见问题解答:“异常有多快?”
【发布时间】:2011-12-27 19:20:52
【问题描述】:

我只是在查看 Python 常见问题解答,因为在另一个问题中提到了它。之前从未真正详细看过它,我遇到了this question:“异常有多快?”:

try/except 块非常有效。实际上捕获异常是昂贵的。在 2.0 之前的 Python 版本中,通常使用这个习语:

try:
    value = mydict[key]
except KeyError:
    mydict[key] = getvalue(key)
    value = mydict[key]

我对 “捕获异常很昂贵” 部分感到有点惊讶。这仅指那些except 将异常保存在变量中的情况,还是一般所有excepts(包括上面示例中的那个)?

我一直认为使用如图所示的这样的习语会非常pythonic,尤其是在Python中“请求宽恕比获得许可更容易”。关于 SO 的许多答案也通常遵循这个想法。

捕捉异常的性能真的那么差吗?在这种情况下,是否应该遵循 LBYL(“跳前看”)?

(请注意,我并没有直接讨论常见问题解答中的示例;还有许多其他示例,您只需查找异常而不是检查之前的类型。)

【问题讨论】:

    标签: python performance exception try-catch


    【解决方案1】:

    捕获异常昂贵的,但异常应该是异常(读取,不经常发生)。如果异常很少见,try/catch 比 LBYL 快。

    以下示例在键存在和不存在时使用异常和 LBYL 对字典键进行查找:

    import timeit
    
    s = []
    
    s.append('''\
    try:
        x = D['key']
    except KeyError:
        x = None
    ''')
    
    s.append('''\
    x = D['key'] if 'key' in D else None
    ''')
    
    s.append('''\
    try:
        x = D['xxx']
    except KeyError:
        x = None
    ''')
    
    s.append('''\
    x = D['xxx'] if 'xxx' in D else None
    ''')
    
    for i,c in enumerate(s,1):
        t = timeit.Timer(c,"D={'key':'value'}")
        print('Run',i,'=',min(t.repeat()))
    

    输出

    Run 1 = 0.05600167960596991       # try/catch, key exists
    Run 2 = 0.08530091918578364       # LBYL, key exists (slower)
    Run 3 = 0.3486251291120652        # try/catch, key doesn't exist (MUCH slower)
    Run 4 = 0.050621117060586585      # LBYL, key doesn't exist
    

    在通常情况下也不例外,try/catch 与 LBYL 相比“效率极高”。

    【讨论】:

    • 几乎就是我要说的。它使用异常来控制您的正常程序流程,这非常昂贵。
    • 做一些快速的数学运算,数字表明只有在您预计最多 8.95% 的时间(大约 11 次调用中的 1 次)找不到密钥的情况下才应该使用 try/except。我没有空间推导出我使用的方程式,但是:让cbn = 正常情况下的分支方法成本,cbx = 例外情况下的分支方法成本,ctn = 正常情况下的尝试成本/例外case, ctx = try/except in exception case 的成本,px = 例外情况发生的概率;那么对于所有px <= (ctn-cbn)/(ctn-ctx+cbx-cbn),从长远来看,try/except 方法会更快。
    • 只是一个附录——因为测量和计算这类事情本身往往很耗时,我尝试使用 python 的经验法则,任何发生超过 1/8 次的事情都是不是“例外”情况,可能应该使用另一种方法——这在设计 api 和编写代码时也被证明是有用的。
    • “例外应该是例外”是一个很好的口头禅。我经常忘记它。
    【解决方案2】:

    显然,成本取决于实施,但我不会担心。无论如何,这不太可能有关系。标准协议在最奇怪的地方引发异常(想想StopIteration),所以无论你喜欢与否,你都会被引发和捕捉的包围。

    在 LBYL 和 EAFP 之间进行选择时,请考虑代码的可读性,而不是关注微优化。如果可能,我会避免类型检查,因为它可能会降低代码的通用性。

    【讨论】:

    • 是的,避免过早优化。
    【解决方案3】:

    如果找不到密钥的情况非常特殊,我建议使用“get”方法,该方法在所有情况下都提供恒定的速度:

    s.append('''\
    x = D.get('key', None)
    ''')
    
    s.append('''\
    x = D.get('xxx', None)
    ''')
    

    【讨论】:

    • 您可以改用dict.setdefault。但话又说回来,代码只是常见问题解答中的一个简单示例。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-26
    • 2011-02-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多