>>> from env_helper import info; info()
页面更新时间: 2022-08-29 22:10:42
运行环境:
    Linux发行版本: Debian GNU/Linux 11 (bullseye)
    操作系统内核: Linux-5.10.0-17-amd64-x86_64-with-glibc2.31
    Python版本: 3.9.2

7.3. 异常中的函数

函 数

描 述

warnings.filterwarnings(action,category=Warning, ...)

用于过滤警告

warnings.warn(message, category=None)

用于发出警告

7.3.1. 异常和函数的联系

异常和函数有着天然的联系。如果不处理函数中引发的异常,它将向上传播到调用函数的地 方。如果在那里也未得到处理,异常将继续传播,直至到达主程序(全局作用域)。如果主程序中也没有异常处理程序,程序将终止并显示栈跟踪消息。来看一个示例:

>>> def faulty():
...
raise Exception('Something is wrong')
...
>>> def ignore_exception():
...
faulty()
...
>>> def handle_exception():
...
try:
...
faulty()
...
except:
...
print('Exception handled')
...
>>> ignore_exception()
Traceback (most recent call last):
    File '<stdin>', line 1, in ?
    File '<stdin>', line 2, in ignore_exception
    File '<stdin>', line 2, in faulty
Exception: Something is wrong
>>> handle_exception()
Exception handled

如你所见,faulty中引发的异常依次从faulty和ignore_exception向外传播,最终导致显示 一条栈跟踪消息。调用handle_exception时,异常最终传播到handle_exception,并被这里的 try/except语句处理。

7.3.2. 异常之禅

异常处理并不是很复杂。如果你知道代码可能引发某种异常,且不希望出现这种异常时程序 终止并显示栈跟踪消息, 可添加必要的 try/excepttry/finally 语句(或结合使用)来处理它。

有时候,可使用条件语句来达成异常处理实现的目标,但这样编写出来的代码可能不那么自 然,可读性也没那么高。 另一方面,有些任务使用 if/else 完成时看似很自然,但实际上使用 try/except 来完成要好得多。

下面来看两个示例:

假设有一个字典,你要在指定的键存在时打印与之相关联的值,否则什么都不做。实现这种 功能的代码可能类似于下面这样:

>>> def describe_person(person):
>>>     print('Description of', person['name'])
>>>     print('Age:', person['age'])
>>>     if 'occupation' in person:
>>>         print('Occupation:', person['occupation'])

如果你调用这个函数,并向它提供一个包含姓名Throatwobbler Mangrove和年龄42(但不包 含职业)的字典,输出将如下:

Description of Throatwobbler Mangrove
Age: 42

如果你在这个字典中添加职业camper,输出将如下:

Description of Throatwobbler Mangrove
Age: 42
Occupation: camper

这段代码很直观,但效率不高( 虽然这里的重点是代码简洁 ),因为 它必须两次查找 ’occupation’键:一次检查这个键是否存在(在条件中),另一次获取这个键关联的值,以便将 8 其打印出来。下面是另一种解决方案:

>>> def describe_person(person):
>>>     print('Description of', person['name'])
>>>     print('Age:', person['age'])
>>>     try:
>>>         print('Occupation:', person['occupation'])
>>>     except KeyError:
>>>         pass

在这里,函数直接假设存在’occupation’键。如果这种假设正确,就能省点事:直接获取并 打印值,而无需检查这个键是否存在。如果这个键不存在,将引发KeyError异常,而except子句 将捕获这个异常。

你可能发现,检查对象是否包含特定的属性时,try/except也很有用。例如,假设你要检查 一个对象是否包含属性write,可使用类似于下面的代码:

try:
    obj.write
except AttributeError:
    print('The object is not writeable')
else:
    print('The object is writeable')

在这里,try子句只是访问属性write,而没有使用它来做任何事情。如果引发了AttributeError 异常,说明对象没有属性write,否则就说明有这个属性。这种解决方案可替代使用getattr的解决方案,而且更自然。具体使用哪种解决方案,在很大程度上取决于个人喜好。

请注意,这里在效率方面的提高并不大(实际上是微乎其微)。一般而言,除非程序存在性能 方面的问题,否则不应过多考虑这样的优化。关键是在很多情况下,相比于使用if/else,使用 try/except语句更自然,也更符合Python的风格。因此你应养成尽可能使用try/except语句的习惯。

7.3.3. 不那么异常的情况

如果你只想发出警告,指出情况偏离了正轨,可使用模块warnings中的函数warn。

>>> from warnings import warn
>>>
>>> warn("I've got a bad feeling about this.")

警告只显示一次。如果再次运行最后一行代码,什么事情都不会发生。 如果其他代码在使用你的模块,可使用模块warnings中的函数filterwarnings来抑制你发出的警告(或特定类型的警告), 并指定要采取的措施,如 errorignore

>>> from warnings import filterwarnings
>>> filterwarnings("ignore")
>>> warn("Anyone out there?")
>>> filterwarnings("error")
>>> warn("Something is very wrong!")
---------------------------------------------------------------------------

UserWarning                               Traceback (most recent call last)

Cell In [4], line 5
      3 warn("Anyone out there?")
      4 filterwarnings("error")
----> 5 warn("Something is very wrong!")


UserWarning: Something is very wrong!

如你所见,引发的异常为UserWarning。发出警告时,可指定将引发的异常(即警告类别), 但必须是Warning的子类。如果将警告转换为错误,将使用你指定的异常。另外,还可根据异常 来过滤掉特定类型的警告。

>>> filterwarnings("error")
>>> warn("This function is really old...", DeprecationWarning)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
DeprecationWarning: This function is really old...
>>> filterwarnings("ignore", category=DeprecationWarning)
>>> warn("Another deprecation warning.", DeprecationWarning)
>>> warn("Something else.")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UserWarning: Something else.
>>> filterwarnings("error")
>>> warn("This function is really old...", DeprecationWarning)
---------------------------------------------------------------------------

DeprecationWarning                        Traceback (most recent call last)

Cell In [12], line 2
      1 filterwarnings("error")
----> 2 warn("This function is really old...", DeprecationWarning)


DeprecationWarning: This function is really old...
>>> filterwarnings("ignore", category=DeprecationWarning)
>>> warn("Another deprecation warning.", DeprecationWarning)
>>> warn("Something else.")
---------------------------------------------------------------------------

UserWarning                               Traceback (most recent call last)

Cell In [11], line 1
----> 1 warn("Something else.")


UserWarning: Something else.

除上述基本用途外,模块warnings还提供了一些高级功能。如果你对此感兴趣,请参阅库参考手册。

7.3.4. 小结

本章介绍了如下重要主题。

  • 异常对象:异常情况(如发生错误)是用异常对象表示的。对于异常情况,有多种处理 方式;如果忽略,将导致程序终止。

  • 引发异常:可使用raise语句来引发异常。它将一个异常类或异常实例作为参数,但你也 可提供两个参数(异常和错误消息)。如果在except子句中调用raise时没有提供任何参数, 它将重新引发该子句捕获的异常。

  • 自定义的异常类:你可通过从Exception派生来创建自定义的异常。

  • 捕获异常:要捕获异常,可在try语句中使用except子句。在except子句中,如果没有指 定异常类,将捕获所有的异常。你可指定多个异常类,方法是将它们放在元组中。如果 向except提供两个参数,第二个参数将关联到异常对象。在同一条try/except语句中,可 包含多个except子句,以便对不同的异常采取不同的措施。

  • else子句:除except子句外,你还可使用else子句,它在主try块没有引发异常时执行。

  • finally:要确保代码块(如清理代码)无论是否引发异常都将执行,可使用try/finally, 并将代码块放在finally子句中。

  • 异常和函数:在函数中引发异常时,异常将传播到调用函数的地方(对方法来说亦如此)。

  • 警告:警告类似于异常,但(通常)只打印一条错误消息。你可指定警告类别,它们是