在python中使用try vs if


145

测试变量具有值时,是否有理由确定要使用哪个tryif构造?

例如,有一个函数可以返回列表或不返回值。我想在处理结果之前先检查一下。以下哪一项更可取,为什么?

result = function();
if (result):
    for r in result:
        #process items

要么

result = function();
try:
    for r in result:
        #process items
except TypeError:
    pass;

相关讨论:

在Python中检查成员是否存在


请记住,如果您的#process items节可能抛出TypeError,则需要使用另一个try:except:块。对于此特定示例,我只使用if:
richo

Answers:


237

您通常会听到Python鼓励EAFP风格(“请求宽恕比许可容易”)而不是LBYL风格(“跨越前先看”)。对我来说,这是效率和可读性的问题。

在您的示例中(例如,该函数不是返回列表或空字符串,而是返回列表或None),如果您希望99%的时间result实际上包含可迭代的内容,则可以使用该try/except方法。如果异常确实是例外,它将更快。如果resultNone的时间超过50%,则使用if的可能会更好。

为了通过一些测量来支持这一点:

>>> import timeit
>>> timeit.timeit(setup="a=1;b=1", stmt="a/b") # no error checking
0.06379691968322732
>>> timeit.timeit(setup="a=1;b=1", stmt="try:\n a/b\nexcept ZeroDivisionError:\n pass")
0.0829463709378615
>>> timeit.timeit(setup="a=1;b=0", stmt="try:\n a/b\nexcept ZeroDivisionError:\n pass")
0.5070195056614466
>>> timeit.timeit(setup="a=1;b=1", stmt="if b!=0:\n a/b")
0.11940114974277094
>>> timeit.timeit(setup="a=1;b=0", stmt="if b!=0:\n a/b")
0.051202772912802175

因此,尽管一条if语句总是要花您很多钱,但设置一个try/except块几乎是免费的。但是当Exception实际发生时,成本要高得多。

道德:

  • try/except用于流程控制完全可以(和“ pythonic”),
  • 但当Exceptions实际上是例外时,这才有意义。

从Python文档中:

东亚自由贸易区

寻求宽恕比允许容易。这种通用的Python编码风格假设存在有效的键或属性,并且在假设被证明为假的情况下捕获异常。这种干净快捷的样式的特点是存在许多 tryexcept声明。该技术与 许多其他语言(例如C)通用的LBYL风格形成对比。


1
谢谢。我看到在这种情况下,其原理可以是对结果的期望。
artdanil

6
....这就是为什么很难为Python进行真正的优化JIT的原因之一。正如最近在Beta版LuaJIT 2中所证明的那样,动态语言可以非常快地实现。但它在很大程度上取决于最初的语言设计及其所鼓励的风格。(在相关说明中,语言设计是为什么即使最好的JavaScirpt JIT都无法与LuaJIT 1进行比较的原因,更不用说2了)
Javier

1
@ 2rs2ts:我自己也做了类似的计时。在Python 3中,try/except它比if key in d:键在字典中的情况快25%。当键不在字典中时,这比预期的要慢得多,并且与此答案保持一致。
Tim Pietzcker

5
我知道这是一个老答案:但是,使用1/1与timeit之类的语句并不是一个好选择,因为它们会被优化(请dis.dis('1/1')注意,不会发生除法)。
Andrea Corbellini 2015年

1
这也取决于您愿意执行的操作。单个除法速度很快,捕获错误的代价较高。但是即使在可接受异常处理成本的情况下,也要仔细考虑您要计算机执行的操作。如果不管结果如何,操作本身都非常昂贵,并且有一种廉价的方法可以判断成功是否可能:请使用它。示例:不要仅仅为了意识到没有足够的空间而复制文件的一半。
Bachsau

14

您的函数不应返回混合类型(即列表或空字符串)。它应该返回一个值列表或一个空列表。然后,您无需进行任何测试,即您的代码折叠为:

for r in function():
    # process items

2
我完全同意你的观点。但是,该功能不是我的,我只是在使用它。
artdanil

2
@artdanil:因此,您可以将该函数包装在编写的函数中,该函数的工作方式类似于Brandon Corfman所考虑的函数。
quamrana

24
这就引出了一个问题:包装器应该使用if还是try来处理不可迭代的情况?
jcdyer

@quamrana正是我想要做的。因此,正如@jcd指出的那样,问题是关于我应该如何处理包装函数中的情况。
artdanil

12

如果我提供的代码乍一看不明显,请忽略我的解决方案,而您必须在代码示例之后阅读说明。

我可以假定“没有返回值”意味着返回值为None吗?如果是,或者如果“ no value”是boolean值,则可以执行以下操作,因为您的代码实际上将“ no value”视为“请勿迭代”:

for r in function() or ():
    # process items

如果function()返回的结果不是True,则对空元组进行迭代,即不运行任何迭代。这本质上是LBYL。


4

您的第二个示例已损坏-该代码将永远不会引发TypeError异常,因为您可以遍历字符串和列表。遍历空字符串或列表也是有效的-它将执行循环主体0次。


4

以下哪一项更可取,为什么?

在这种情况下,“跨越前先看”更可取。使用异常方法,TypeError可能会在循环主体中的任何位置发生,并且会被捕获并丢弃,这不是您想要的,而且会使调试变得棘手。

(尽管我同意布兰登·考夫曼的观点:返回“无项目”而不是空列表的做法是无效的。这是Java编码器的不愉快习惯,不应在Python或Java中看到。)


4

通常,我得到的印象是例外应保留用于特殊情况。如果result预期永远不会为空(例如,如果磁盘崩溃等可能为空),则第二种方法很有意义。另一方面,如果result在正常情况下清空是完全合理的,请使用if语句进行更有意义。

我想到了(更常见的)情况:

# keep access counts for different files
file_counts={}
...
# got a filename somehow
if filename not in file_counts:
    file_counts[filename]=0
file_counts[filename]+=1

而不是等效的:

...
try:
    file_counts[filename]+=1
except KeyError:
    file_counts[filename]=1

这是Tim Pietzcker提到的方法之间的差异的一个示例:第一个是LBYL,第二个是EAFP
Managu

请注意,++在python 中不起作用的快速提示,请+= 1改用。
tgray

3

bobince明智地指出,包装第二种情况也可以在循环中捕获TypeErrors,这不是您想要的。如果您确实想尝试一下,可以在循环之前测试它是否可迭代

result = function();
try:
    it = iter(result)
except TypeError:
    pass
else:
    for r in it:
        #process items

如您所见,这非常难看。我不建议这样做,但出于完整性考虑。


在我眼中,故意遇到类型错误总是不好的编码风格。开发人员应该知道期望什么,并准备无例外地处理这些值。每当操作执行了应做的事情(从程序员的角度来看)并且预期结果是列表or时None,请始终使用is Noneor 来检查结果is not None。另一方面,为合法结果引发Exceptions也很不好。异常是意料之外的事情。示例:str.find()如果未找到任何内容,则返回-1,因为搜索本身已完成而没有错误。
Bachsau

1

就性能而言,对于通常不引发异常的代码使用try块比每次使用if语句要快。因此,决定取决于发生特大案件的可能性。


-5

作为一般经验法则,切勿使用try / catch或任何异常处理工具来控制流程。即使幕后的迭代是通过引发StopIteration异常来控制的,您仍然应该优先选择第一个代码段而不是第二个代码段。


7
在Java中是这样。Python非常重视EAFP。
fengb

3
这仍然是一种奇怪的做法。根据我的经验,大多数程序员的大脑都跳过了EAFP。他们最终想知道为什么选择了某个路径。话虽这么说,现在有打破每个规则的时间和地点。
ilowe
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.