【问题标题】:Cheap exception handling in Python?Python中廉价的异常处理?
【发布时间】:2010-10-10 12:50:09
【问题描述】:

我在早先的answer 中读到,Python 中的异常处理成本很低,因此我们不应该进行前置条件检查。

我以前没有听说过这个,但我对 Python 比较陌生。异常处理是动态调用,静态返回,而if语句是静态调用,静态返回。

如何进行检查是不好的,而 try-except 是好的,似乎是相反的。谁能给我解释一下?

【问题讨论】:

标签: python performance exception-handling


【解决方案1】:

不要为小事出汗。您已经选择了其中一种较慢的脚本语言,因此尝试优化操作码对您没有多大帮助。选择 Python 等解释型动态语言的原因是优化您的时间,而不是 CPU。

如果您使用通用语言习语,那么您将看到快速原型设计和简洁设计的所有好处,并且随着新版本 Python 的发布和计算机硬件的升级,您的代码自然会运行得更快。

如果您遇到性能问题,请分析您的代码并优化您的慢速算法。但与此同时,在异常情况下使用异常,因为这将使您最终按照这些思路进行的任何重构都变得容易得多。

【讨论】:

  • 我不同意。 Python 足够快。它与 C 和汇编相同。在瓶颈处将 C 嵌入到 Python 中,就像在瓶颈处将程序集嵌入到 C 中一样。
  • 奇怪的是,我同意你在“我不同意”之后所说的一切。 :)
  • 我认为他不喜欢 Python 被称为“[慢] 脚本语言”。
  • 对不起,它慢。使用另一种语言来支持它因为太慢并不能改变事实。我几乎可以用任何语言调用 C - 这并不意味着我必须这样做。
  • “你在 Python 中嵌入 C 遇到瓶颈”并不意味着 Python 很快,只是意味着它足够灵活,可以在失败时寻求帮助。
【解决方案2】:

您可能会发现这篇文章很有帮助:Try / Except Performance in Python: A Simple Test Patrick Altman 在其中进行了一些简单的测试,以了解在各种情况下的性能是什么预条件检查(在这种情况下特定于字典键)并仅使用异常。如果您想对其进行调整以测试其他条件,也提供了代码。

他得出的结论:

从这些结果来看,我认为这是公平的 快速确定一些 结论:

  1. 如果元素不存在的可能性很高,则 你最好检查一下 使用 has_key。
  2. 如果你不打算对异常做任何事情,如果它是 提高,那你最好不要 把一个有例外
  3. 如果该元素很可能确实存在,那么有一个非常 使用 try/except 的轻微优势 阻止而不是使用 has_key, 但是,优势非常微弱。

【讨论】:

  • has_key 不是一个好主意。 AFAIK,(dict 中的某个键)/dict.get 更好。
  • 我建议不要将要捕获的异常放在 except: 中,因为这样您就不会注意到引发任何其他异常并且调试更加困难。但是恕我直言,无论如何都应该很少使用例外。如果您可以使用测试。
  • 我同意上述两个 cmets。作为一个额外的数据点,我只是在上面链接的基准脚本中添加了使用if 'key' in d 而不是if d.has_key('key') 的案例,我发现它始终是最快的方法——即使在密钥存在于词典。当然,这个测试不是很科学,但很有趣。我在 x86_64 上使用 2.7.3。
  • @Jay:您应该注意测试是使用time.time() 而不是timeit 模块执行的。时序测试以可能遇到许多陷阱而闻名。使用timeit 安排 Patrick 的测试时间会更有说服力。
  • @Jay:另外,Patrick 比较了不可比较的东西:他的“try”函数从字典中获取值,而他的“if”函数没有。他的测试无法得出很多结论。
【解决方案3】:

抛开其他人所说的绩效衡量标准,指导原则的结构通常是“请求宽恕比请求许可更容易”与“三思而后行”。

考虑这两个 sn-ps:

# Look before you leap
if not os.path.exists(filename):
    raise SomeError("Cannot open configuration file")
f = open(filename)

对比

# Ask forgiveness ...
try:
  f = open(filename)
except IOError:
  raise SomeError("Cannot open configuration file")

等价的?并不真地。操作系统是多用途系统。如果在 'exists' 和 'open' 调用测试之间删除了文件会怎样?

如果文件存在但不可读怎么办?如果它是目录名而不是文件怎么办。可能有许多可能的故障模式,检查所有这些模式需要做很多工作。特别是因为“打开”调用已经检查并报告了所有这些可能的故障。

指导方针应该是减少状态不一致的机会,最好的方法是使用异常而不是测试/调用。

【讨论】:

    【解决方案4】:

    “谁能给我解释一下?”

    视情况而定。

    这是一种解释,但它没有帮助。你的问题源于你的假设。由于现实世界与您的假设相冲突,这一定意味着您的假设是错误的。没有太多解释,但这就是你问的原因。

    “异常处理是动态调用,静态返回,而if语句是静态调用,静态返回。”

    “动态调用”是什么意思?搜索处理程序的堆栈帧?我假设这就是你所说的。并且“静态调用”以某种方式定位 if 语句之后的块。

    也许这种“动态调用”并不是操作中成本最高的部分。或许 if 语句表达式求值比简单的“try-it-and-fail”稍微贵一些。

    事实证明,Python 的内部完整性检查与您的 if 语句几乎相同,并且无论如何都必须进行。由于 Python 总是会进行检查,因此您的 if 语句(大部分)是多余的。

    您可以在http://docs.python.org/c-api/intro.html#exceptions 中阅读有关低级异常处理的信息。


    编辑

    更重要的是:if vs. except 辩论无关紧要。

    由于异常很便宜,因此不要将它们标记为性能问题。

    使用使您的代码清晰有意义。不要在这样的微优化上浪费时间。

    【讨论】:

    • 只有在你意识到这无关紧要之后,这场辩论才无关紧要。我的意思是,只有当我们知道性能差异可以忽略时,我们才能放心地让它保持原样,专注于代码质量本身。如果异常处理比常规的“if”慢一个数量级,那么我们就不能说它没关系。抽象泄漏。大多数时候我们不应该担心它们,但我们不能忽视盲目忽视性能问题的潜在危害。
    【解决方案5】:

    使用 Python,很容易检查不同的速度可能性 - 了解timeit module

    ... 示例会话(使用命令行),比较使用 hasattr() 与 try/except 测试缺失和当前对象属性的成本。

    % timeit.py 'try:' '  str.__nonzero__' 'except AttributeError:' '  pass'
    100000 loops, best of 3: 15.7 usec per loop
    % timeit.py 'if hasattr(str, "__nonzero__"): pass'
    100000 loops, best of 3: 4.26 usec per loop
    % timeit.py 'try:' '  int.__nonzero__' 'except AttributeError:' '  pass'
    1000000 loops, best of 3: 1.43 usec per loop
    % timeit.py 'if hasattr(int, "__nonzero__"): pass'
    100000 loops, best of 3: 2.23 usec per loop
    

    这些计时结果显示在hasattr() 的情况下,引发异常很慢,但执行测试比不引发异常慢。因此,就运行时间而言,使用异常处理异常情况是有意义的。

    编辑:命令行选项-n 将默认为足够大的计数,以便运行时间有意义。一个quote from the manual

    如果没有给出 -n,则通过尝试连续的 10 次方来计算合适的循环次数,直到总时间至少为 0.2 秒。

    【讨论】:

    • 只是想知道,第三次测试的尝试次数是十倍吗?
    • 添加了手册中的另一个引用 - 第三次测试对于 100000 个循环来说太快了。
    【解决方案6】:

    我也是 python 初学者。虽然我不能说为什么在该答案的上下文中异常处理被称为便宜,但这是我的想法:

    请注意,使用 if-elif-else 进行检查必须每次都评估一个条件。异常处理,包括对异常处理程序的搜索仅在异常情况下发生,这在大多数情况下可能很少见。这是一个明显的效率增益。 正如 Jay 所指出的,当密钥很可能不存在时,最好使用条件逻辑而不是异常。这是因为如果密钥大部分时间都不存在,这不是异常情况。

    也就是说,我建议你不要担心效率,而要担心意义。当你想决定某件事时,使用异常处理来检测异常情况和检查条件。昨天我被 S.Lott 提醒过意义的重要性。

    举例:

    def xyz(key):
       dictOb = {x:1, y:2, z:3}
       #Condition evaluated every time
       if dictOb.has_key(key):  #Access 1 to dict
            print dictOb[key]  #Access 2
    

    对比

    #Exception mechanism is in play only when the key isn't found.
    def xyz(key):
       dictOb = {x:1, y:2, z:3}
       try:
            print dictOb[key]  #Access 1
       except KeyError:
            print "Not Found"
    

    总体而言,有一些代码可以处理某些事情,比如丢失的键,以防万一需要异常处理,但是在大多数时间不存在键的情况下,你真正的要做的是确定密钥是否存在=> if-else。 Python 强调并鼓励说出你的意思。

    为什么异常优先于 if-elif ->

    1. 当您查看代码中的 exception 也就是异常/意外情况时,它可以更清楚地表达含义。
    2. 它更简洁,可读性更强。
    3. 更灵活。
    4. 可以用来编写更简洁的代码。
    5. 避免了很多讨厌的检查。
    6. 更易于维护。

    注意 当我们避免使用 try-except 时,会继续引发异常。未处理的异常只是转到默认处理程序。当您使用 try-except 时,您可以自己处理错误。它可能更有效,因为 if-else 需要条件评估,而寻找异常处理程序可能更便宜。即使这是真的,从中获得的收益也太小了,无需考虑。

    希望我的回答对你有帮助。

    【讨论】:

    • 我发现 if 版本实际上更具可读性,特别是如果您使用较新的 'if key in dictOb' 语法。在这里,KeyError 至少很明显;有时无需查找就很难判断异常的含义,而 if 通常是显式的。
    • 好吧,在这个小例子中,它对可读性没有帮助。我只是想说明我的观点。另一方面,当您有许多异常情况时,如果您使用 if-else,它会很快变得混乱。我从个人经验中知道这是个坏主意。
    • *使用 if-else 是个坏主意。学习阅读异常并不难。它比维护凌乱的 if-else 更容易。
    • +1 表示意思,俗话说“程序必须写给人看,只是顺便让机器执行。”
    • @batbrat:我认为坚持任何一种方法都是错误的。我可以看到基于上下文的权衡。在这种情况下,if 没有 try-except 那样“混乱”——意图很明确,源语句更少,并且不会导致解释“KeyError”时的注意力暂时停顿。
    【解决方案7】:

    什么是静态调用和动态调用和返回,为什么你认为 Python 中的调用和返回有什么不同,这取决于你是否在 try/except 块中执行它?即使您没有捕获异常,Python 仍然必须处理可能引发某些问题的调用,因此在处理调用和返回方面对 Python 没有影响。

    Python 中的每个函数调用都涉及将参数压入堆栈并调用可调用对象。在 Python 的内部连线中,调用者跟随每个函数终止,检查成功或异常终止,并相应地处理它。换句话说,如果你认为当你在一个 try/except 块中时有一些额外的处理,而当你不在其中时会以某种方式跳过,那你就错了。我认为这就是您的“静态”与“动态”区别的意义所在。

    此外,这是一个风格问题,有经验的 Python 开发人员会很好地阅读异常捕获,因此当他们在调用周围看到适当的 try/except 时,它比条件检查更具可读性。

    【讨论】:

      【解决方案8】:

      正如 S.Lott 所说,一般信息是 try/except 不会造成伤害,因此您应该在合适的时候随意使用它。

      这种辩论通常被称为“LBYL 与 EAFP”——即“先看再跳”与“请求宽恕比请求许可更容易”。 Alex Martelli 在这里提出这个主题:http://mail.python.org/pipermail/python-list/2003-May/205182.html 这场辩论已经将近六年了,但我认为基本问题没有太大变化。

      【讨论】:

        猜你喜欢
        • 2021-09-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-12-11
        • 1970-01-01
        相关资源
        最近更新 更多