python基础——第8章 异常

  • 每个异常都是某个类(比如:除零异常是ZeroDivisionError)的实例。

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

8.2.1 raise语句

要引发异常,可使用raise语句,
下面的示例使用的是内置异常类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 

在第一个示例(raise Exception)中,引发的是通用异常,没有指出出现了什么错误。在第二个示例中,添加了错误消息hyperdrive overload。 有很多内置的异常类,表8-1描述了最重要的几个。在“Python库参考手册”的Built-in Exceptions一节,可找到有关所有内置异常类的描述。

表8-1 一些内置的异常类

类 名 描 述
Exception 几乎所有的异常类都是从它派生而来的
AttributeError 引用属性或给它赋值失败时引发
OSError 操作系统不能执行指定的任务(如打开文件)时引发,有多个子类
IndexError 使用序列中不存在的索引时引发,为LookupError的子类
KeyError 使用映射中不存在的键时引发,为LookupError的子类
NameError 找不到名称(变量)时引发
SyntaxError 代码不正确时引发
TypeError 将内置操作或函数用于类型不正确的对象时引发
ValueError 将内置操作或函数用于这样的对象时引发:其类型正确但包含的值不合适
ZeroDivisionError 在除法或求模运算的第二个参数为零时引发

8.2.2 自定义的异常类

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

class SomeCustomException(Exception): pass 

8.3 捕获异常

为捕获异常并对错误进行处理,可像下面这样重写这个程序:

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来捕获他人所编写函数引发的异常。

8.3.1 不用提供参数

捕获异常后,如果要重新引发它(即继续向上传播),可调用raise且不提供任何参数
为说明这很有用,来看一个能够“抑制”异常ZeroDivisionError的计算器类。如果启用了这种功能,计算器将打印一条错误消息,而不让异常继续传播。在与用户交互的会话中使用这个计算器时,抑制异常很有用;但在程序内部使用时,引发异常是更佳的选择(此时应关闭“抑制”功能)。下面是这样一个类的代码:

class MuffledCalculator:  
	muffled = False 
	def calc(self, expr): 
	try: 
		return eval(expr) 
	except ZeroDivisionError: 
		if self.muffled: 
			print('Division by zero is illegal') 
		else: 
			raise 

如果无法处理异常,在except子句中使用不带参数的raise通常是不错的选择,但有时你可能想引发别的异常。在这种情况下,导致进入except子句的异常将被作为异常上下文存储起来,并出现在最终的错误消息中,
在大多数情况下,更好的选择是使用except Exception as e并对异常对象进行检查。

# 通过使用else子句,可实现8.3.3节所说的循环。 
while True:  
   	try: 
 		x = int(input('Enter the first number: ')) 
 		y = int(input('Enter the second number: ')) 
 		value = x / y 
 		print('x / y is', value) 
   	except: 
    	print('Invalid input. Please try again.') 
   	else: 
 		break 

在这里,仅当没有引发异常时,才会跳出循环(这是由else子句中的break语句实现的)。换而言之,只要出现错误,程序就会要求用户提供新的输入。

8.3.7 最后

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

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

在上述示例中,不管try子句中发生什么异常,都将执行finally子句。为何在try子句之前初始化x呢?因为如果不这样做,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 

8.5 异常之禅

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

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,而没有使用它来做任何事情。如果引发了AttributeError异常,说明对象没有属性write,否则就说明有这个属性。这种解决方案可替代7.2.9节介绍的使用getattr的解决方案,而且更自然。具体使用哪种解决方案,在很大程度上取决于个人喜好。
请注意,这里在效率方面的提高并不大(实际上是微乎其微)。一般而言,除非程序存在性能方面的问题,否则不应过多考虑这样的优化。关键是在很多情况下,相比于使用if/else,使用try/except语句更自然,也更符合Python的风格。因此你应养成尽可能使用try/except语句的习惯 ① 。

8.7 本章介绍的新函数

函 数 描 述
warnings.filterwarnings(action,category=Warning, …) 用于过滤警告
warnings.warn(message, category=None) 用于发出警告

猜你喜欢

转载自blog.csdn.net/acktomas/article/details/84771017