Python中的“try”语句的可选“else”子句的预期用途是什么?

790

try语句的可选else子句的预期用途是什么?


4
大多数答案集中在为什么我们不能将 else 子句中的内容放在 try 子句本身中。问题 https://dev59.com/rlHTa4cB1Zd3GeqPQVT6 具体询问为什么 else 子句代码不能跟在 try 块本身之后,而该问题被重复到这个问题中,但我没有看到对这个问题的清晰回答。我认为 https://dev59.com/rlHTa4cB1Zd3GeqPQVT6#3996378 给出了很好的回答。我还试图阐明各个子句的不同含义,链接为 https://dev59.com/vknSa4cB1Zd3GeqPKQIT#22579805。 - jamadagni
2
如果异常没有触发,在最终清理之前,您希望发生某些事情,而这些事情本身不应该触发相同的异常处理。 - benjimin
5
由于经常忘记 try/elsefor/else 中的 else 所起的作用,我在这些情况下已经将其与 noexceptnobreak 在心理上进行了关联。个人认为这种词汇的过载是一个非常不幸的现象,因为它会使阅读代码的人们想要知道“这个东西到底是做什么的?”通常,一个标志、一个 continuebreak 语句可以用更少的额外行数来传达我想表达的意思,但当然也更清晰(如果这个问题的受欢迎程度是任何迹象的话)。 - Michael Ekoka
23个回答

1064
else块中的语句会在try块的结尾处执行,如果没有异常出现的话。老实说,我从来没有发现需要使用它。
但是,《处理异常》指出:

使用else子句比向try子句添加附加代码更好,因为它避免了意外捕获由try...except语句保护的代码未引发的异常。

所以,如果您有一个可能会引发IOError的方法,并且您想要捕获它引发的异常,但是在第一个操作成功后还有其他事情要做,而您不想从该操作中捕获IOError,您可以编写类似以下内容的代码:
try:
    operation_that_can_throw_ioerror()
except IOError:
    handle_the_exception_somehow()
else:
    # we don't want to catch the IOError if it's raised
    another_operation_that_can_throw_ioerror()
finally:
    something_we_always_need_to_do()

如果你在operation_that_can_throw_ioerror后面加上another_operation_that_can_throw_ioerror(),那么except会捕获第二个调用的错误。如果你把它放在整个try块之后,它将总是被运行,但要等到finally之后才运行。else让你确保:
  1. 只有在没有异常的情况下才运行第二个操作;
  2. finally块之前运行;
  3. 它引发的任何IOError都不会在这里被捕获。

11
请注意,在 try 块中使用的变量可以在 else 块中使用,因此如果您不期望 else 块发生更多异常,应始终考虑使用这种变体。 - WorldSEnder
4
不要紧,因为无论是否有else,try语句中定义的变量在try外面也是可见的。 - Reinderien
63
在Python中,不存在“尝试作用域变量”的概念。变量作用域仅由模块、函数和推导式确定,而不是控制结构。 - mhsmith
11
else 子句允许你编写仅在未引发异常时才有意义的代码;except 子句可以简单地传递。如果将逻辑放在 try 块中,可能会悄悄隐藏代码中的 bug。不要压制未预期的异常。 - Alice Purcell
12
从这个答案中并不清楚“掉到底部”是什么意思 - 这不仅发生在异常情况下,还因为 returncontinuebreak 的存在。 - Antti Haapala -- Слава Україні
显示剩余15条评论

147

使用else命令的一个很大的原因是风格和可读性。通常,将可能引发异常的代码与处理它们的代码紧密相邻是个好主意。例如,请比较以下内容:

try:
    from EasyDialogs import AskPassword
    # 20 other lines
    getpass = AskPassword
except ImportError:
    getpass = default_getpass

try:
    from EasyDialogs import AskPassword
except ImportError:
    getpass = default_getpass
else:
    # 20 other lines
    getpass = AskPassword

如果except不能早期返回或重新抛出异常,第二种方法更好。 如果可能的话,我会这样写:

try:
    from EasyDialogs import AskPassword
except ImportError:
    getpass = default_getpass
    return False  # or throw Exception('something more descriptive')

# 20 other lines
getpass = AskPassword

注意: 此答案从最近发布的重复问题 这里 复制,因此有关"AskPassword"的所有内容都是基于那个问题。


81

Python try-else

try语句的可选else从句的预期用途是在没有异常被处理的情况下,提供更多代码运行的上下文。

这个上下文可以避免意外处理您没有预期的错误。

但重要的是要了解导致else子句运行的精确条件,因为returncontinuebreak语句可能会中断对else的控制流。

总之

如果没有异常并且没有被returncontinuebreak语句中断,则else语句将被执行。

其他答案忽略了最后一部分。

来自官方文档:

如果控制流程离开try子句末尾,就会执行可选的else子句。

(加粗部分是笔者添加的) 脚注如下:

*目前,控制流程在除了异常或执行returncontinuebreak语句的情况下会离开。

它至少需要一个前置的except子句(请参见语法)。所以它真的不是"try-else",而是"try-except-else(-finally)",其中else(和finally)是可选的。

Python教程详细阐述了其预期用途:

try ... except语句有一个可选的else从句,当存在时,必须跟在所有except从句之后。对于必须在try从句不引发异常时执行的代码很有用。例如:

for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
    except OSError:
        print('cannot open', arg)
    else:
        print(arg, 'has', len(f.readlines()), 'lines')
        f.close()

The use of the else clause is better than adding additional code to the try clause because it avoids accidentally catching an exception that wasn’t raised by the code being protected by the try ... except statement.

例子:区分 elsetry 代码块后的代码

如果你处理了一个错误,else 块将不会被执行。例如:

def handle_error():
    try:
        raise RuntimeError('oops!')
    except RuntimeError as error:
        print('handled a RuntimeError, no big deal.')
    else:
        print('if this prints, we had no error!') # won't print!
    print('And now we have left the try block!')  # will print!

现在,

>>> handle_error()
handled a RuntimeError, no big deal.
And now we have left the try block!

2
文档的主要观点是:“使用else子句比将其他代码添加到try子句中更好,因为它避免了意外捕获未被try ... except语句保护的代码引发的异常。” - starriet
我认为这是最好的解释。谢谢Aaron。 - codebusta
@starriet的评论清晰简洁。只是想强调一下,因为这个评论补充了这个(已经很好的)答案。 - Elysiumplain
如果f.close()语句在"finally:"块中,那么使用f=open()的示例会更加清晰(至少对我来说是这样)。 - user3481644
这是从文档中引用的一个例子 - 我宁愿按原样呈现它... - Russia Must Remove Putin

68
一个用途:测试应该引发异常的一些代码。
try:
    this_should_raise_TypeError()
except TypeError:
    pass
except:
    assert False, "Raised the wrong exception type"
else:
    assert False, "Didn't raise any exception"

(实际应将此代码抽象为更通用的测试。)

30

尝试-异常-否则结构很适合将EAFP模式鸭子类型相结合:

try:
  cs = x.cleanupSet
except AttributeError:
  pass
else:
  for v in cs:
    v.cleanup()

你可能认为这个天真的代码没问题:

try:
  for v in x.cleanupSet:
    v.clenaup()
except AttributeError:
  pass

这是一个意外隐藏代码中严重错误的好方法。我在那里打错了“cleanup”,但是会让我知道的“AttributeError”被吞噬了。更糟糕的是,如果我写得正确,但是清理方法偶尔传递了一个属性名称错误的用户类型,导致它在一半时默默地失败并留下一个未关闭的文件,那该怎么办?祝你好运调试。

22

如果有必要进行清理工作,并且即使发生异常也必须执行该清理工作,那么下面的方法非常有用:

try:
    data = something_that_can_go_wrong()
except Exception as e: # yes, I know that's a bad way to do it...
    handle_exception(e)
else:
    do_stuff(data)
finally:
    clean_up()

在我看来,这是最好的表达方式。使用文件I/O的示例(在上面的某个地方显示)很好地阐述了这一点。基本上,else:将代码从try块中提取出来,在finally之前执行,如果没有异常(如无法打开文件)的话。 - user3481644

13

尽管你现在无法想到它的用途,但可以肯定地是必须有一种用途的。这里是一个缺乏想象力的示例:

有了else

a = [1,2,3]
try:
    something = a[2]
except IndexError:
    print("out of bounds")
else:
    print(something)

没有else

try:
    something = a[2]
except IndexError:
    print("out of bounds")

if "something" in locals():
    print(something)

如果没有错误抛出,这里定义了变量something。您可以将其从try块外移除,但是那样需要进行一些混乱的检测以确定变量是否已定义。


6
在try块内使用something = a[2]; print something有什么问题? - S.Lott
@ S.Lott 没错,但如果有人发送给您一个列表,而您不想显示数据,因为它不够长,那么这可能是损坏的吗? - Unknown
14
"S. Lott: 'print something' 可能会引发你不想捕获的异常。" - Darius Bacon
我看不出有什么区别。如果我得到一个越界异常,它会打印“越界”。明白了。如果我得到其他异常,它将无法被此代码块捕获。如果没有异常,行为是打印某个值,该值是a[2]。我不明白这个例子中的else语句有什么作用。 - S.Lott
3
当打印“something”的值时,可能会在其__str__()方法中引发错误。 虽然在这个例子中该值实际上只是2,但你也可以指出这里没有越界异常。 - Darius Bacon
对于这个(假设的)例子,在 except 代码块中我会简单地写 something = 'out of bound',这样 print(something) 就总是会输出正确的响应;-) - wovano

11

PEP 380中有一个很好的try-else示例。基本上,它涉及在算法的不同部分中进行不同的异常处理。

大概是这样的:

try:
    do_init_stuff()
except:
    handle_init_suff_execption()
else:
    try:
        do_middle_stuff()
    except:
        handle_middle_stuff_exception()

这样可以让你在异常发生的位置更近的地方编写异常处理代码。


8

来自错误和异常 # 处理异常 - docs.python.org

The try ... except statement has an optional else clause, which, when present, must follow all except clauses. It is useful for code that must be executed if the try clause does not raise an exception. For example:

for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
    except IOError:
        print 'cannot open', arg
    else:
        print arg, 'has', len(f.readlines()), 'lines'
        f.close()

The use of the else clause is better than adding additional code to the try clause because it avoids accidentally catching an exception that wasn’t raised by the code being protected by the try ... except statement.


8
try:
    statements # statements that can raise exceptions
except:
    statements # statements that will be executed to handle exceptions
else:
    statements # statements that will be executed if there is no exception

例子:

try:
    age=int(input('Enter your age: '))
except:
    print ('You have entered an invalid value.')
else:
    if age <= 21:
        print('You are not allowed to enter, you are too young.')
    else:
        print('Welcome, you are old enough.')

输出:
>>> 
Enter your age: a
You have entered an invalid value.
>>> RESTART
>>> 
Enter your age: 25
Welcome, you are old enough.
>>> RESTART
>>> 
Enter your age: 13
You are not allowed to enter, you are too young.
>>> 

来源: https://geek-university.com/python/the-try-except-else-statements/

try 语句用于捕获异常,except 子句用于处理异常。如果在 try 块中发生异常,那么该异常会被 except 子句捕获并处理。

else 子句是 tryexcept 子句之外的部分。如果没有发生任何异常,则 else 子句中的代码将被执行。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接