Python基础教程——异常

  • 异常
    • 异常是什么
    • 让事情沿你指定的轨道出错
      • raise 语句
      • 自定义的异常类
    • 捕获异常
      • 不用提供参数
      • 多个 except 子句
      • 一箭双雕
      • 捕获对象
      • 一网打尽
      • 万事大吉时
      • 最后
    • 异常和函数
    • 异常之禅
    • 不那么异常的情况
    • 小结

异常

异常事件可能是错误(如试图除以零),也可能是通常不会发生的事情。为处理这些异常事件,可在每个可能发生这些事件的地方都使用条件语句。

Python提供功能强大的替代解决方案——异常处理机制

异常是什么

Python使用异常对象来表示异常状态,并在遇到错误时引发异常。
异常对象未被处理(或捕获)时,程序将终止并显示一条错误消息(traceback)。

>>> 1/0
Traceback (most recent call last):File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero

事实上,每个异常都是某个类(这里是 ZeroDivisionError )的实例。

能以各种方式引发和捕获这些实例,从而逮住错误并采取措施,而不是放任整个程序失败。

让事情沿你指定的轨道出错

如何自主地引发异常,还有如何创建异常,学习如何处理这些异常。

raise 语句

要引发异常,可使用 raise 语句,并将一个类(必须是 Exception 的子类)或实例作为参数。
将类作为参数时,将自动创建一个实例。

内置异常类 Exception

>>> raise Exception
Traceback (most recent call last):File "<stdin>", line 1, in ?
Exception
>>> raise Exception('hyperdrive overload')
Traceback (most recent call last):File "<stdin>", line 1, in ?
Exception: hyperdrive overload

在“Python库参考手册”的Built-in Exceptions一节,可找到有关所有内置异常类的描述。
这些异常类都可用于 raise 语句中。

>>> raise ArithmeticError
Traceback (most recent call last):File "<stdin>", line 1, in ?
ArithmeticError

自定义的异常类

虽然内置异常涉及的范围很广,能够满足很多需求,但有时你可能想自己创建异常类。

如何创建异常类呢?就像创建其他类一样,但务必直接或间接地继承 Exception (这意味着从任何内置异常类派生都可以)。
因此,自定义异常类的代码类似于下面这样:

class SomeCustomException(Exception): pass

也可在自定义异常类中添加方法。

捕获异常

异常比较有趣的地方是可对其进行处理,通常称之为捕获异常。

可使用 try / except 语句。

假设你创建了一个程序,让用户输入两个数,再将它们相除,如下所示:

x = int(input('Enter the first number: '))
y = int(input('Enter the second number: '))
print(x / y)

这个程序运行正常,直到用户输入的第二个数为零。

Enter the first number: 10
Enter the second number: 0
Traceback (most recent call last):File "exceptions.py", line 3, in ?print(x / y)
ZeroDivisionError: integer division or modulo by zero

为捕获这种异常并对错误进行处理(这里只是打印一条对用户更友好的错误消息),可像下面这样重写这个程序:

try:x = int(input('Enter the first number: '))y = int(input('Enter the second number: '))print(x / y)
except ZeroDivisionError:print("The second number can't be zero!")

使用 try / except 的话只需要一个错误处理程序。

注意:
异常从函数向外传播到调用函数的地方。
如果在这里也没有被捕获,异常将向程序的最顶层传播。
这意味着你可使用 try / except 来捕获他人所编写函数引发的异常。

不用提供参数

捕获异常后,如果要重新引发它(即继续向上传播),可调用 raise 且不提供任何参数(也可显式地提供捕获到的异常)。

一个类的代码示例:

class MuffledCalculator:muffled = Falsedef calc(self, expr):try:return eval(expr)except ZeroDivisionError:if self.muffled:print('Division by zero is illegal')else:raise
注意:
发生除零行为时,如果启用了“抑制”功能,方法 calc 将(隐式地)返回 None 。
换而言之,如果启用了“抑制”功能,就不应依赖返回值。

下面的示例演示了这个类的用法(包括启用和关闭了抑制功能的情形):

>>> calculator = MuffledCalculator()
>>> calculator.calc('10 / 2')
5.0
>>> calculator.calc('10 / 0') # 关闭了抑制功能
Traceback (most recent call last): File "<stdin>", line 1, in ?File "MuffledCalculator.py", line 6, in calc
return eval(expr)File "<string>", line 0, in ?
ZeroDivisionError: integer division or modulo by zero
>>> calculator.muffled = True
>>> calculator.calc('10 / 0')
Division by zero is illegal

关闭抑制功能时,捕获了异常 ZeroDivisionError ,但继续向上传播它。

如果无法处理异常,在 except 子句中使用不带参数的 raise 通常是不错的选择,但有时可能想引发别的异常。

在这种情况下,导致进入 except 子句的异常将被作为异常上下文存储起来,并出现在最终的错误消息中,如下所示:

>>> try:
...     1/0
... except ZeroDivisionError:
...     raise ValueError
...
Traceback (most recent call last):File "<stdin>", line 2, in <module>
ZeroDivisionError: division by zero

在处理上述异常时,引发了另一个异常:

Traceback (most recent call last):File "<stdin>", line 4, in <module>
ValueError

可使用 raise … from … 语句来提供自己的异常上下文,也可使用 None 来禁用上下文。

>>> try:
...     1/0
... except ZeroDivisionError:
...     raise ValueError from None
...
Traceback (most recent call last):File "<stdin>", line 4, in <module>
ValueError

多个 except 子句

运行前一节的程序,并在提示时输入一个非数字值,将引发另一种异常。

Enter the first number: 10
Enter the second number: "Hello, world!"
Traceback (most recent call last):File "exceptions.py", line 4, in ?print(x / y)
TypeError: unsupported operand type(s) for /: 'int' and 'str'

由于该程序中的 except 子句只捕获 ZeroDivisionError 异常,这种异常将成为漏网之鱼,导致程序终止。
为同时捕获这种异常,可在 try / except 语句中再添加一个 except 子句。

try:x = int(input('Enter the first number: '))y = int(input('Enter the second number: '))print(x / y)
except ZeroDivisionError:print("The second number can't be zero!")
except TypeError:print("That wasn't a number, was it?")

异常处理并不会导致代码混乱,而添加大量的 if 语句来检查各种可能的错误状态将导致代码的可读性极差。

一箭双雕

如果要使用一个 except 子句捕获多种异常,可在一个元组中指定这些异常,如下所示:

try:x = int(input('Enter the first number: '))y = int(input('Enter the second number: '))print(x / y)
except (ZeroDivisionError, TypeError, NameError):print('Your numbers were bogus ...')

如果用户输入字符串、其他非数字值或输入的第二个数为零,都将打印同样的错误消息。

另一种解决方案是不断地要求用户输入数字,直到能够执行除法运算为止。

在 except 子句中,异常两边的圆括号很重要。
一种常见的错误是省略这些括号,这可能导致你不想要的结果。

捕获对象

要在 except 子句中访问异常对象本身,可使用两个而不是一个参数。

注意,即便是在你捕获多个异常时,也只向 except 提供了一个参数——一个元组。

下面的示例程序打印发生的异常并继续运行:

try:x = int(input('Enter the first number: '))y = int(input('Enter the second number: '))print(x / y)
except (ZeroDivisionError, TypeError) as e:print(e)

在这个小程序中, except 子句也捕获两种异常,但由于你同时显式地捕获了对象本身,因此可将其打印出来,让用户知道发生了什么情况。

一网打尽

即使程序处理了好几种异常,还是可能有一些漏网之鱼。

如,对于前面执行除法运算的程序,如果用户在提示时不输入任何内容就按回车键,将出现一条错误消息,还有一些相关问题出在什么地方的信息(栈跟踪)。
如下所示:

Traceback (most recent call last):...
ValueError: invalid literal for int() with base 10: ''

这种异常未被 try / except 语句捕获。

在这些情况下,与其使用并非要捕获这些异常的 try / except 语句将它们隐藏起来,还不如让程序马上崩溃,因为这样你就知道什么地方出了问题。

如果就是要使用一段代码捕获所有的异常,只需在 except 语句中不指定任何异常类即可。

try:x = int(input('Enter the first number: '))y = int(input('Enter the second number: '))print(x / y)
except:print('Something wrong happened ...')

现在,用户想怎么做都可以。

Enter the first number: "This" is *completely* illegal 123
Something wrong happened ...

像这样捕获所有的异常很危险,因为这不仅会隐藏你有心理准备的错误,还会隐藏你没有考虑过的错误。

在大多数情况下,更好的选择是使用 except Exception as e 并对异常对象进行检查。

万事大吉时

在有些情况下,在没有出现异常时执行一个代码块很有用。

为此,可像条件语句和循环一样,给 try / except 语句添加一个 else 子句。

try:print('A simple task')
except:print('What? Something went wrong?')
else:print('Ah ... It went as planned.')

如果运行这些代码,输出将如下:

A simple task
Ah ... It went as planned.

通过使用 else 子句,可实现循环。

while True:try:x = int(input('Enter the first number: '))y = int(input('Enter the second number: '))value = x / yprint('x / y is', value)
except:print('Invalid input. Please try again.')
else:break

在这里,仅当没有引发异常时,才会跳出循环(这是由 else 子句中的 break 语句实现的)。

换而言之,只要出现错误,程序就会要求用户提供新的输入。
下面是这些代码的运行情况:

Enter the first number: 1
Enter the second number: 0
Invalid input. Please try again.
Enter the first number: 'foo'
Enter the second number: 'bar'
Invalid input. Please try again.
Enter the first number: baz
Invalid input. Please try again.
Enter the first number: 10
Enter the second number: 2
x / y is 5

一种更佳的替代方案是使用空的 except 子句来捕获所有属于类 Exception (或其子类)的异常。

如果使用 except Exception as e ,就可在这个小型除法程序中打印更有用的错误消息。

while True:try:x = int(input('Enter the first number: '))y = int(input('Enter the second number: '))value = x / yprint('x / y is', value)
except Exception as e:print('Invalid input:', e)print('Please try again')
else:break

下面是这个程序的运行情况:

Enter the first number: 1
Enter the second number: 0
Invalid input: integer division or modulo by zero
Please try again
Enter the first number: 'x' Enter the second number: 'y'
Invalid input: unsupported operand type(s) for /: 'str' and 'str'
Please try again
Enter the first number: quuux
Invalid input: name 'quuux' is not defined
Please try again
Enter the first number: 10
Enter the second number: 2
x / y is 5

最后

还有 finally 子句,可用于在发生异常时执行清理工作。这个子句是与 try 子句配套的。

x = None
try:x = 1 / 0
finally:print('Cleaning up ...')del x

在上述示例中,不管 try 子句中发生什么异常,都将执行 finally 子句。

ZeroDivisionError 将导致根本没有机会给它赋值,进而导致在 finally 子句中对其执行 del 时引发未捕获的异常。

如果运行这个程序,它将在执行清理工作后崩溃。

Cleaning up ...
Traceback (most recent call last):File "C:\python\div.py", line 4, in ?x = 1 / 0
ZeroDivisionError: integer division or modulo by zero

虽然使用 del 来删除变量是相当愚蠢的清理措施,但 finally 子句非常适合用于确保文件或网络套接字等得以关闭。

也可在一条语句中同时包含 try 、 except 、 finally 和 else (或其中的3个)。

try:1 / 0
except NameError:print("Unknown variable")
else:print("That went well!")
finally:print("Cleaning up.")

异常和函数

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

一个示例:

>>> 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_exceptionFile '<stdin>', line 2, in faulty
Exception: Something is wrong
>>> handle_exception()
Exception handled

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

异常之禅

有时,可使用条件语句来达成异常处理实现的目标,但这样编写出来的代码可能不那么自然,可读性也没那么高。
另一方面,有些任务使用 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’ 键:一次检查这个键是否存在(在条件中),另一次获取这个键关联的值,以便将其打印出来。

下面是另一种解决方案:

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 ,而没有使用它来做任何事情。

这种解决方案可替代 getattr 的解决方案,而且更自然。

不那么异常的情况

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

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

警告只显示一次。如果再次运行最后一行代码,什么事情都不会发生。

如果其他代码在使用你的模块,可使用模块 warnings 中的函数 filterwarnings 来抑制你发出的警告(或特定类型的警告),并指定要采取的措施,如" error “或” ignore "。

>>> from warnings import filterwarnings
>>> filterwarnings("ignore")
>>> warn("Anyone out there?")
>>> filterwarnings("error")
>>> warn("Something is very wrong!")
Traceback (most recent call last):File "<stdin>", line 1, in <module>
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.

除上述基本用途外,模块 warnings 还提供了一些高级功能。可参阅库参考手册。

小结

本章介绍的新函数

学习参考资料:

《Python基础教程》 第3版

《Python基础教程》学习笔记——异常相关推荐

  1. Java 核心技术卷 II(第 8 版) – 读书笔记 – 第 1 章(下)

    22.一旦获得了一个 Charset,就可以在 Java 的 Unicode 和指定的编码格式之间进行转化,下面以 GBK 和 Unicode 之间做为例子. 从 Unicode 到 GBK: imp ...

  2. 《Java 核心技术卷1 第10版》学习笔记------异常

    异常处理的任务就是将控制权从错误产生的地方转移给能够处理这种情况的错误处理器 . 7.1.1 异常分类 在 Java 程序设计语言中, 异常对象都是派生于 Throwable 类的一个实例 . 稍后还 ...

  3. 《Java 核心技术卷1 第10版》学习笔记------对象克隆【对象拷贝】

    由于克隆并不太常见,而且有关的细节技术性很强,你可能只是想稍做了解,等真正需要时再深人学习. 先来回忆为一个包含对象引用的变量建立副本时会发生什么 .原变量和副本都是同一个对象的引用: Employe ...

  4. 《Java 核心技术卷1 第10版》学习笔记 ------ 泛型【进阶】

    这部分主要是结合 Java 虚拟机实现泛型的原理进一步研究如何更好的使用泛型. 8.5 泛型代码和虚拟机 虚拟机没有泛型类型对象---所有对象都属于普通类.所以编译器在编译的时候会进行类型擦除操作. ...

  5. 《Java 核心技术卷1 第10版》学习笔记 ------ 泛型【基础】

    泛型从Java SE 5.0 中开始出现,是 Java 程序设计语言从 1.0 版本发布以来,变化最大的部分. 使用泛型机制编写的程序代码要比那些杂乱地使用 Object 变量,然后再进行强制类型转换 ...

  6. 《Java 核心技术卷1 第10版》学习笔记------调试技巧

    调试器是 Eclipse . NetBeans 这类专业集成开发环境的一部分 . 在启动调试器之前, 本节先给出一些有价值的建议 . 1 ) 可以用下面的方法打印或记录任意变量的值 : System. ...

  7. 《Java 核心技术卷1 第10版》学习笔记------日志

    日志 API 的优点: 可以很容易地取消全部日志记录, 或者仅仅取消某个级别的日志, 而且打开和关闭这个操作也很容易 . 可以很简单地禁止日志记录的输出, 因此, 将这些日志代码留在程序中的开销很小 ...

  8. 《Java 核心技术卷1 第10版》学习笔记------ 接口(Interface)

    接口技术作用:主要描述类具有什么功能,而并不给出每个功能的是的实现. Java的继承(inheritance)是不支持的多继承的,但是Java接口是支持多继承的. 一个 Java 类实现一个接口类就必 ...

  9. 《Java 核心技术卷1 第10版》学习笔记------ 对象包装器、自动装箱、拆箱

    有时, 需要将 int 这样的基本类型转换为对象. 所有的基本类型都冇一个与之对应的类.例如,Integer 类对应基本类型 int.通常, 这些类称为包装器 ( wrapper ) 这些对象包装器类 ...

  10. 《Java 核心技术卷1 第10版》学习笔记------ Object类的 hashCode 方法

    散列码( hash code ) 是由对象导出的一个整型值.散列码是没有规律的.如果 x 和 y 是两个不同的对象, x.hashCode( ) 与 y.hashCode( ) 基本上不会相同. 在表 ...

最新文章

  1. 在Eclipse或MyEclipse中安装findbugs插件
  2. 大数据背景下的高职院校信息化建设探索
  3. android布局加色,android – 如何以编程方式将LinearLayout添加背景颜色,并将权重添加到另一个布局...
  4. [USACO1.5]数字三角形 Number Triangles
  5. 2013\Province_Java_C\2.组素数
  6. 【算法题1】上台阶问题
  7. 批量生成印刷字体字库
  8. 09Oracle Database 数据表数据插入,更新,删除
  9. container_of宏
  10. react接收后端文件_React如何从后端获取数据并渲染到前端?
  11. 苹果回应被18岁学生起诉索赔10亿美元:零售店内未使用面部识别
  12. bzoj 1044: [HAOI2008]木棍分割(二分+DP)
  13. PowerDesigner将建好的数据表格导出到excel
  14. jQuery使用之(二)设置元素的样式
  15. matlab四宫格画图_Matlab绘图初级教程
  16. 用易升升级到Win10后在第三方浏览器无法打开网页的解决办法
  17. C++实现类似QT中的计时器QTime类(CQTime)
  18. 什么是人工智能数据采集?
  19. 【dSPACE】从0开启dSPACE之路(2)具体上手操作步骤
  20. 趣味测试小程序源码带流量主广告位开源小程序

热门文章

  1. jquery H5 好用的编辑器umeditor
  2. crontab格式写错出现报错
  3. 简单命令行创建ReactApp
  4. 谷歌人工智能产生自我意识了!聊天记录曝光!机器真的只是机器吗?
  5. (堪比Photoshop的图片处理)Pixelmator Pro激活版
  6. 给大家推荐一个大大的萌妹子,算是我学计算机以来遇到的最喜欢的妹子吧!23333333
  7. vue 使用gsap(TweenMax)
  8. python之shapely库的使用
  9. air什么意思中文_air这个英文中文意思是什么?
  10. 消防工程师培训十大名师介绍