8.1 异常是什么
>>> 1/0
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
<ipython-input-85-05c9758a9c21> in <module>()
----> 1 1/0
ZeroDivisionError: division by zero
如上,Python使用异常对象来表示异常状态,并在遇到错误时引发异常。异常对象未被处理(捕获)时,程序将终止并显示一条错误消息(Traceback)。
每个异常都是每个类(这里是 ZeroDivisionError)的实例。你能以各种方式引发和捕获这些实例,从而逮住错误并采取措施而不是放任整个程序的失败。
8.2 让事情沿你指定的轨道出错
如何自主地引发异常,如何创建异常,如何处理异常
8.2.1 raise 语句
引发异常。
# 增加一个异常并添加了错误信息的注释
>>> raise Exception("这里有一个错误")
---------------------------------------------------------------------------
Exception Traceback (most recent call last)
<ipython-input-89-5f15955ab2d6> in <module>()
----> 1 raise Exception("这里有一个错误")
Exception: 这里有一个错误
Exception 是Python的内置异常类,表示通用异常,没有指出什么错误。其他的内置异常类,都是从 Exception 派生出来的。
Python内置异常类层次关系图:
BaseException
+-- SystemExit
+-- KeyboardInterrupt
+-- GeneratorExit
+-- Exception #本章介绍的异常类
+-- StopIteration
+-- StandardError
| +-- BufferError
| +-- ArithmeticError
| | +-- FloatingPointError
| | +-- OverflowError
| | +-- ZeroDivisionError
| +-- AssertionError
| +-- AttributeError
| +-- EnvironmentError
| | +-- IOError
| | +-- OSError
| | +-- WindowsError (Windows)
| | +-- VMSError (VMS)
| +-- EOFError
| +-- ImportError
| +-- LookupError
| | +-- IndexError
| | +-- KeyError
| +-- MemoryError
| +-- NameError
| | +-- UnboundLocalError
| +-- ReferenceError
| +-- RuntimeError
| | +-- NotImplementedError
| +-- SyntaxError
| | +-- IndentationError
| | +-- TabError
| +-- SystemError
| +-- TypeError
| +-- ValueError
| +-- UnicodeError
| +-- UnicodeDecodeError
| +-- UnicodeEncodeError
| +-- UnicodeTranslateError
+-- Warning
+-- DeprecationWarning
+-- PendingDeprecationWarning
+-- RuntimeWarning
+-- SyntaxWarning
+-- UserWarning
+-- FutureWarning
+-- ImportWarning
+-- UnicodeWarning
+-- BytesWarning
8.2.2 自定义的异常类
# 自定义异常类,可以添加方法
class SomeException(Exception):
pass
Ps:如何使用自定义的异常类?
8.3 捕获异常
# 输入两个数作除法
x = int(input("输入除数:"))
y = int(input("输入被除数:"))
print("x/y = ",x/y)
----------
输入除数:1
输入被除数:0
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
<ipython-input-92-aa1da2ce6cb0> in <module>()
1 x = int(input("输入除数:"))
2 y = int(input("输入被除数:"))
----> 3 print("x/y = ",x/y)
ZeroDivisionError: division by zero
# 捕获异常,并对错误进行处理(此处打印出错误消息)
try:
x = int(input("输入除数:"))
y = int(input("输入被除数:"))
print("x/y = ",x/y)
except ZeroDivisionError:
print("错误:被除数不能为 0 !")
----------
输入除数:1
输入被除数:0
错误:被除数不能为 0 !
8.3.1 不用提供参数
捕获到异常后,如果要重新引发它(向上传播),可调用 raise 且不提供任何参数。
下面提供了一个能够“抑制“异常的计算器类。如果启用功能,将打印一条错误信息,而不让异常继续传播;如果未启用,则异常继续传播。
# 抑制异常计算器
class MuffledCalculator:
muffled = False
def calc(self,expr):
try:
return eval(expr)
except ZeroDIvisionError:
if self.muffled: # 如果 muffled 为 True,则异常被抑制,输出错误信息
print('被除数不能为 0 !')
else: # 如果 muffled 为 False,则异常能够继续传播
raise
如果捕获了异常还想要引发其他异常,导致进入 except 字句的异常将被作为异常上下文存储起来,并出现在最终的错误消息中。如下所示:
try:
1/0
except ZeroDivisionError("被除数不能为 0 "):
raise ValueError("这是引发的第二个异常")
----------
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
<ipython-input-100-a442a12bba7f> in <module>()
1 try:
----> 2 1/0
3 except ZeroDivisionError("被除数不能为 0 "):
ZeroDivisionError: division by zero
During handling of the above exception, another exception occurred:
---------------------------------------------------------------------------
# 处理完上面的异常后,引发了这个异常
TypeError Traceback (most recent call last)
<ipython-input-100-a442a12bba7f> in <module>()
1 try:
2 1/0
----> 3 except ZeroDivisionError("被除数不能为 0 "):
4 raise ValueError("这是引发的第二个异常")
TypeError: catching classes that do not inherit from BaseException is not allowed
----------
8.3.2 多个 except 子句
# 捕获异常,并对错误进行处理(此处打印出错误消息)
try:
x = int(input("输入除数:"))
y = int(input("输入被除数:"))
print("x/y = ",x/y)
except ZeroDivisionError:
print("错误:被除数不能为 0 !")
----------
输入除数:5
输入被除数:啊
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-112-693d5bd77e92> in <module>()
2 try:
3 x = int(input("输入除数:"))
----> 4 y = int(input("输入被除数:"))
5 print("x/y = ",x/y)
6 except ZeroDivisionError:
ValueError: invalid literal for int() with base 10: '啊'
通过上面的代码我们发现,在输入一个非数字的‘啊‘后,引发了另外一种异常。且该程序中的 except 只能捕获 ZeroDivisionError 异常,这种异常将成为漏网之鱼,导致程序终止。为同时捕获这种异常,可再添加一个 exceept 子句。
# 捕获异常,并对错误进行处理(此处打印出错误消息)
try:
x = int(input("输入除数:"))
y = int(input("输入被除数:"))
print("x/y = ",x/y)
except ZeroDivisionError:
print("错误:被除数不能为 0 !")
except TypeError:
print("嘿老兄,我可不认得除了 0123456789 之外的东西!")
⚠️ 异常处理并不会导致代码混乱,而挺假大量的 if 语句来检查各种可能的错误状态将导致代码的可读性极差。
8.3.3 一箭双雕
如果要使用一个 except 子句捕获多重异常,可在一个元组中指定这些异常,如下所示:
try:
x = int(input("输入除数:"))
y = int(input("输入被除数:"))
print("x/y=",x/y)
except(ZeroDivisionError, TypeError, NameError):
print("your numbers were bogus ... ")
8.3.4 捕获对象
要在 except 子句中访问异常对象本身,可使用两个而不是一个参数。(⚠️即便是在你捕获多个异常时,也只是想 except 提供了一个参数——一个元组。)需要让程序继续运行并记录错误(可能只是向用户显示)时,这很有用。下面的示例程序打印发生的异常并继续运行:
# 捕获异常并访问异常对象本身
try:
x = int(input("输入除数:"))
y = int(input("输入被除数:"))
print("x/y=",x/y)
except(ZeroDivisionError, TypeError, NameError) as e:
print(e)
----------
# 访问异常对象本身 division by zero
输入除数:1
输入被除数:0
division by zero
8.3.5 except Exception as e 捕获漏网之鱼
即使程序处理了好几种异常,还是可能有一些漏网之鱼。那么,此时不如让程序马上崩溃,这样就知道了什么地方除了问题。
然而,如果就是要用一段代码捕获所有的异常的化,只需在 except 子句中不制定任何异常类即可。
# 捕获任意异常
try:
x = int(input("输入除数:"))
y = int(input("输入被除数:"))
print("x/y=",x/y)
except:
print("Somthing wrong happened ..." )
但是这样根本不是到错在哪儿了,因此,更好的选择是使用
except Exception as e
并对异常现象进行检查。这样将让不是从 Exception 派生而来的为数不多的异常成为漏网之鱼。【详情见 8.2.1 的“Python内置异常类层次关系图“】
8.3.6 循环处理异常
# 发生异常时,根据提示的异常信息调整输入,直至输入正确没有异常后跳出循环
while True:
try:
x = int(input("输入除数:"))
y = int(input("输入被除数:"))
print("x/y=",x/y)
except Exception as e: # 当出现异常时,返回异常对象的异常信息
print("错误原因:",e )
print("请重新输入:")
else: # 只有当没有异常时,才跳出 while 循环
break
8.3.7 Finally
finally 子句,可用于在发生异常时执行‘毁灭世界‘工作。这个子句是与 try 子句配套的。
# 当发生异常,执行 finally 子句
try:
x = 1/0
finally:
print('正在毁灭世界...')
del x
NameError 为什么返回的是 ZeroDivisionError?
# 可以包含try、except、else(或其中的3个)
try:
1/0
except NameError:
print('Unknow variable')
else:
print("That went well!")
finally:
print('我正在毁灭世界...')
----------
我正在毁灭世界...
---------------------------------------------------------------------------
# NameError 为什么返回的是 ZeroDivisionError?
ZeroDivisionError Traceback (most recent call last)
<ipython-input-11-ee56fa0deca8> in <module>()
1 try:
----> 2 1/0
3 except NameError:
4 print('Unknow variable')
5 else:
ZeroDivisionError: division by zero
–
8.4 异常和函数
如果不处理函数中引发的异常,它将一直向上传播到调用函数的地方,如果仍未得到处理,异常将继续传播,直至达到主程序(全局作用域)。如果主程序中也没有异常处理程序,程序将中止并显示栈跟踪消息。
8.5 异常之禅
给出如下代码,进行简介性优化
def describe_persion(persion):
print("名字:",persion['name'])
print("年龄:",persion['age'])
if "occupation" in persion:
print("职业:",persion['occupation'])
# 代码待优化问题:在条件语句中,需要对persion'进行两次查找。
# 在这里,如果字典中没有'occupation'键,将引发异常 KeyError,对异常进行 pass 处理,忽略了异常。
def describe_persion(persion):
'引用了异常机制来优化条件语句'
print("名字:",persion['name'])
print("年龄:",persion['age'])
try:
print("职业:",persion['occupation'])
except KeyError: # KeyEror:使用映射中不存在的键时引发
pass # 忽略这个异常
----------
>>> persion = {'name':'Wei Wu','age':18}
>>> describe_persion(persion)
名字: Wei Wu
年龄: 18
'定义一个类'
class Class:
def set_name(self,name):
self.name = name
>>> c = Class()
>>> c.set_name = 'Wei Wu'
'检查对象是否包含特定的属性'
try:
c.name
except AttributeError: # 引用属性给它赋值失败时触发
print("The object is not writeable")
else:
print("The object is writeable")
这里采用 try/except 语句的方式可替代在 7.2.9节中的方式,但这两种方式在效率提升方面相差微乎其微。
>>> hasattr(c,'name')
True
在很多情况下,相比于使用 if/else,使用 try/except 更加自然、更加符合 Python 风格。
8.6 不那么异常的情况
如果只是想发出警告,指出情况偏离了正轨,可使用模块 warnings 中的函数 warn。
from warnings import warn
warn("I\'ve got a bad feeling about this")
----------
'警告只显示一次。'
/Applications/anaconda3/lib/python3.6/site-packages/ipykernel_launcher.py:2: UserWarning: I've got a bad feeling about this
通过 filterwarnings 函数来抑制发出的特定警告
>>> from warnings import filterwarnings
>>> filterwarnings("ignore") # ignore(忽视),会忽视掉警告。
>>> warn("Anyone out there?") # 这条警告不会显示
>>> filterwarnings("error") # 异常会显示
>>> warn("Something is wrong!")
----------
---------------------------------------------------------------------------
UserWarning Traceback (most recent call last)
<ipython-input-63-df3903e537a5> in <module>()
1 filterwarnings('error')
----> 2 warn("Something is wrong!")
UserWarning: Something is wrong!
发出警告时,可以指定将要引发的异常(必须是 Warning的子类),也可根据异常来过滤掉特定类型的警告
+-- Warning
+-- DeprecationWarning
+-- PendingDeprecationWarning
+-- RuntimeWarning
+-- SyntaxWarning
+-- UserWarning
+-- FutureWarning
# 引发了 DeprecationWarning 的警告
>>> filterwarnings('error')
>>> warn('The function is really old ...',DeprecationWarning)
---------------------------------------------------------------------------
DeprecationWarning Traceback (most recent call last)
<ipython-input-64-a4397c211662> in <module>()
1 filterwarnings('error')
----> 2 warn('The function is really old ...',DeprecationWarning)
DeprecationWarning: The function is really old ...
# 抑制了 DeprecationWarning 的警告
>>> filterwarnings('ignore',category = DeprecationWarning)
>>> warn('Another deprecation warning',DeprecationWarning)
# 发出警告
>>> warn("Sonething wrong.")
---------------------------------------------------------------------------
UserWarning Traceback (most recent call last)
<ipython-input-66-ea06672df1e5> in <module>()
----> 1 warn('something wrong')
UserWarning: something wrong
8.7 小结
异常对象:异常情况是用异常对象表示的。对于异常情况,有多种处理方式;如果忽略,将导致程序终止。
引发异常:可使用 raise 语句来引发异常,它将一个异常类或异常实例作为参数,但你也可提供两个参数(异常和错误消息)[8.2.1]。如果在 excepy 子句中调用 raise 时没有提供任何参数,它将重新引发该子句捕获的异常 [8.3.1]。
自定义的异常类:你可以通过从 Exception 派生来创建自定义的异常 [8.2.2]。
捕获异常:要捕获异常,可在 try 语句中使用 except 子句 [8.3]。在 except 子句中,如果没有指定异常类,将捕获所有异常 [8.3.5]。你可指定多个异常类,方法是将它们放在元组中 [8.3.3] 。如果向 except 提供两个参数,第二个参数将关联到异常对象 [8.3.4]。在同一条 try/except 语句中,可包含多个 except 子句,以便对不同的异常采取不同的措施 [8.3.2]。
else 子句: 除了 except 子句外,你还可以使用 else 子句,它在主 try 块没有引发异常时执行 [8.3.6]。
finally:要确保代码块(如执行毁灭世界程序)无论是否引发异常都将执行,可使用 try/finally 并将代码块(毁灭世界)放在 finally 子句中 [8.3.7]。
异常和函数:在函数中引发异常时,异常将传播到调用函数的地方。[8.4]
- 警告:警告类似于异常,但(通常)只打印一条错误消息。你可指定警告类别,它们是 Warning 的子类。[8.6]
个人总结
(1)异常对象 - 8。1
Python 使用异常对象来表示异常状态,并在程序遇到错误时引发异常;如果异常未被处理,程序将被中断并返回异常信息。异常对象如下所示,其中 Exception 和 Warning 是在本章中所学习到的。
Python内置异常类层次关系图:
BaseException
+-- SystemExit
+-- KeyboardInterrupt
+-- GeneratorExit
+-- Exception #本章介绍的异常类
+-- StopIteration
+-- StandardError
| +-- BufferError
| +-- ArithmeticError
| | +-- FloatingPointError
| | +-- OverflowError
| | +-- ZeroDivisionError
| +-- AssertionError
| +-- AttributeError
| +-- EnvironmentError
| | +-- IOError
| | +-- OSError
| | +-- WindowsError (Windows)
| | +-- VMSError (VMS)
| +-- EOFError
| +-- ImportError
| +-- LookupError
| | +-- IndexError
| | +-- KeyError
| +-- MemoryError
| +-- NameError
| | +-- UnboundLocalError
| +-- ReferenceError
| +-- RuntimeError
| | +-- NotImplementedError
| +-- SyntaxError
| | +-- IndentationError
| | +-- TabError
| +-- SystemError
| +-- TypeError
| +-- ValueError
| +-- UnicodeError
| +-- UnicodeDecodeError
| +-- UnicodeEncodeError
| +-- UnicodeTranslateError
+-- Warning
+-- DeprecationWarning
+-- PendingDeprecationWarning
+-- RuntimeWarning
+-- SyntaxWarning
+-- UserWarning
+-- FutureWarning
+-- ImportWarning
+-- UnicodeWarning
+-- BytesWarning
(2)引发异常(raise) - 8。2
若要引发异常,使用 raise 语句
# raise 后指定一个 Exception 子类,并可添加异常信息
raise Exception_class[('指定的异常信息')]
(3)自定义异常类 - 8。3
可用 Exception 派生自定义异常类
class SomeCustomException(Exception):pass
(4)捕获异常
- 使用 try/except 捕获异常。
- 当 except 子句未指定异常类时,将捕获所有异常。
- 若指定了异常类,将捕获指定异常,同时可以指定多个异常,在一个元组中指定多个异常类
- 可以由多个 except 子句来分别捕获不同的异常类
- 为 try/except 语句添加 else 子句,如果没有异常则执行另外一个模块
- except class_Error as e ,将返回对象本身,可以让程序记住或向用户显示错误信息
- finally,不论是否发生异常都执行 finally 子句,在执行完 finally 子句后引发异常(举例:某公交司机在心脏病发作死亡前拉下公交车制动挽救一车人性命)
(5)异常和函数
当函数内部的异常未得到处理,异常将会传播至调用函数的地方,程序将中止并显示栈跟踪消息。
(6)警告
如果想发出警告,调用 python 模块 warnings 中的函数 warn,通常只打印一条错误消息
warnings.warn(message,category = None)
warn("这是一个警告")
如果选择性的警告,即抑制某些警告,使用模块 warnings 中的 filterwarnings 函数, filterwarnings 可以选择指定的警告(当然必须是 Warnings 的子类,见上面的 异常类层次图)
warnings.filterwarnings(action,category = Warning_class)
#[action = ('error','ignore')],error:引发, ignore:抑制