笔记内容:Python3 错误和异常
笔记日期:2017-11-12
Python3 错误和异常
- 语法错误
- 异常
- 异常处理
- 抛出异常
- 用户自定义异常
- finally语句
- 预定义的清理行为
异常处理机制概述:
异常处理,是编程语言或计算机硬件里的一种机制,用于处理软件或信息系统中出现的异常状况(即超出程序正常执行流程的某些特殊条件)。通过异常处理,我们可以对用户在程序中的非法输入进行控制和提示,以防程序崩溃。
就好比一个旅游景点,每到一个有可能出现问题情况的地方就会设置一个处理问题的处理点,不同的问题有不同的处理点,例如花粉过敏有花粉过敏的处理点,摔伤有摔伤的处理点等。程序也是如此会出现各种各样的错误,同理不同的异常错误有不同的异常错误处理方法。
各种编程语言在处理异常方面具有非常显著的不同点(错误检测与异常处理区别在于:错误检测是在正常的程序流中,处理不可预见问题的代码,例如一个调用操作未能成功结束)。某些编程语言有这样的函数:当输入存在非法数据时不能被安全地调用,或者返回值不能与异常进行有效的区别。例如,C语言中的atoi函数(ASCII串到整数的转换)在输入非法时可以返回0。在这种情况下编程者需要另外进行错误检测(可能通过某些辅助全局变量如C的errno),或进行输入检验(如通过正则表达式),或者共同使用这两种方法。
在python中我们可以通过try-except语句来捕捉异常,语法错误的话开发工具都会有提示的。
语法错误
语法错误或者称之为解析错误,是初学者经常碰到的,如下实例:
>>> while True print(‘Hello world‘) File "<stdin>", line 1, in ? while True print(‘Hello world‘) ^ SyntaxError: invalid syntax
这个例子中,函数 print() 被检查到有错误,是它前面缺少了一个冒号( : )。
解释器会指出了出错的一行,并且在最先找到的错误的位置标记了一个小小的箭头。
异常
即便代码的语法是正确的,但是在运行它的时候,也有可能发生错误。运行期间检测到的错误被称为异常,例如不能被0整除错误,或者空指针异常。
大多数的异常都不会被程序处理,都以错误信息的形式展现在这里:
>>> 10 * (1/0) # 不能被0整除异常 Traceback (most recent call last): File "<stdin>", line 1, in ? ZeroDivisionError: division by zero >>> 4 + spam*3 Traceback (most recent call last): File "<stdin>", line 1, in ? NameError: name ‘spam‘ is not defined # 变量未声明异常 >>> ‘2‘ + 2 # 类型异常 Traceback (most recent call last): File "<stdin>", line 1, in ? TypeError: Can‘t convert ‘int‘ object to str implicitly
异常会有不同的类型,这些类型都作为信息的一部分打印出来,以上例子中的类型有
ZeroDivisionError(不能被0整除异常),NameError( 变量未声明异常) 和
TypeError(类型异常),这些异常类型能提示开发人员发生的是什么样的异常,这样就可以分析错误发生在何处。
错误信息的前面部分显示了异常发生的上下文,并以调用栈的形式显示具体信息。
异常处理
在python中通过try-except语句来处理异常,例如将可能会出现不能被0整除异常的代码写在try代码块里,try代码块里的代码执行过程中出现异常后,就会执行except代码块里的代码,代码示例:
# 会出现异常的代码写在try里 try: num=10/0 print(num) # 如果上面的代码出现异常这句代码不会被执行 except: print("出现异常了!") # try里的代码出现异常后就会执行这里的代码
运行结果:
出现异常了!
try语句按照如下方式工作;
- 首先,执行try子句(在关键字try和关键字except之间的语句)
- 如果没有异常发生,会忽略except子句,try子句执行后就结束。
- 如果在执行try子句的过程中发生了异常,那么try子句余下的部分将被忽略。如果异常的类型和 except 之后的名称相符,那么对应的except子句将被执行。最后执行 try 语句之后的代码。
- 如果一个异常没有与任何的except匹配,那么这个异常将会被抛出。
一个 try 语句可以包含多个except子句,分别来处理不同的特定的异常。但是最多只有一个分支会被执行。
以上示例的是最简单的使用方式,能够捕获所有类型的异常,称之为通用异常陷阱。如果需要捕捉特定的异常,可以在except中声明异常的类型,那么这个陷阱就只能捕获你所声明的异常类型,但是可以在末尾写上一个通用异常陷阱,没有被特定的陷阱所捕获的异常最后就会被通用异常陷阱所捕获。如果你在except中声明了一个异常类型,可以通过as关键字赋值给一个变量,通过这个变量可以打印出错误信息,代码示例:
try: num = 10 / 0 print(num) except ZeroDivisionError as err: # 赋值给err变量 print("出现异常:", err) except TypeError: # 声明一个指定的异常类型 print("出现类型异常!") except: # 末尾可以使用一个通用异常 print("出现异常了!")
运行结果:
出现异常: division by zero
一个except子句可以同时处理多个异常,这些异常将被放在一个括号里成为一个元组,例如:
except (RuntimeError, TypeError, NameError):
使用raise关键字,可以将异常再次抛出,会抛出到解释器中,代码示例:
try: num = 10 / 0 print(num) except ZeroDivisionError as err: # 赋值给err变量 print("出现异常:", err) raise except TypeError: # 声明一个指定的异常类型 print("出现类型异常!") except: # 末尾可以使用一个通用异常 print("出现异常了!")
运行结果:
出现异常: division by zero Traceback (most recent call last): File "E:/PythonProject/TestExcept.py", line 2, in <module> num = 10 / 0 ZeroDivisionError: division by zero
try except 语句还有一个可选的else子句,这个子句必须放在所有的except子句之后。这个子句将在try子句没有发生任何异常的时候执行。例如:
try: num = 1+1 except ZeroDivisionError as err: # 赋值给err变量 print("出现异常:", err) raise except TypeError: # 声明一个指定的异常类型 print("出现类型异常!") except: # 末尾可以使用一个通用异常 print("出现异常了!") else : print("没有出现异常!")
运行结果:
没有出现异常!
使用 else 子句比把所有的语句都放在 try 子句里面要好,这样可以避免一些意想不到的、而except又没有捕获的异常。
异常处理并不仅仅处理那些直接发生在try子句中的异常,而且还能处理子句中调用的函数(甚至间接调用的函数)里抛出的异常。例如:
def errorTest(): return 1/0 try: num = errorTest() except ZeroDivisionError as err: # 赋值给err变量 print("出现异常:", err) except TypeError: # 声明一个指定的异常类型 print("出现类型异常!") except: # 末尾可以使用一个通用异常 print("出现异常了!") else : print("没有出现异常!")
运行结果:
出现异常: division by zero
抛出异常
上面示例也用到了raise 关键字,通过这个关键字可以抛出异常到外部。也可以使用此关键字在代码中抛出特定的异常,如果这个关键字写在except里,并且没有指定要抛出的异常,那么这个raise 就会抛出这个陷阱里的异常,代码示例:
try: num=10/0 except ZeroDivisionError: raise
运行结果:
Traceback (most recent call last): File "E:/PythonProject/TestExcept.py", line 3, in <module> num=10/0 ZeroDivisionError: division by zero
使用raise 关键字抛出指定的异常示例:
i=0 j=1 if i!=0: k=j/i else: print("抛出一个异常:") raise ZeroDivisionError
运行结果:
Traceback (most recent call last): File "E:/PythonProject/TestExcept.py", line 8, in <module> raise ZeroDivisionError ZeroDivisionError
抛出的异常可以指定一个字符串类型的参数,这个参数也会随着异常信息打印出来,代码示例:
i=0 j=1 if i!=0: k=j/i else: print("抛出一个异常:") raise ZeroDivisionError("我是异常")
运行结果:
抛出一个异常: Traceback (most recent call last): File "E:/PythonProject/TestExcept.py", line 9, in <module> raise ZeroDivisionError("我是异常") ZeroDivisionError: 我是异常
用户自定义异常
你可以通过创建一个新的exception类来拥有自己的异常。异常应该继承自 Exception 类,或者直接继承,或者间接继承,例如:
# 这是一个自定义的异常类 >>> class MyError(Exception): # 继承于Exception类 def __init__(self, value): # 这是初始化方法,也就是构造器 self.value = value #这是这个类的属性 def __str__(self): # 类中的每个方法都需要有一个self参数,通过这个参数来获取类属性的值 return repr(self.value) >>> try: raise MyError(2*2) #抛出自定义的异常类 except MyError as e: print(‘My exception occurred, value:‘, e.value) #会被这里捕获 My exception occurred, value: 4 # 运行结果 >>> raise MyError(‘oops!‘) # 抛出自定义的异常类 # 打印的异常信息 Traceback (most recent call last): File "<stdin>", line 1, in ? __main__.MyError: ‘oops!‘
在这个例子中,类 Exception 默认的 _init_( ) 被覆盖。
当创建一个模块有可能抛出多种不同的异常时,一种通常的做法是为这个包建立一个基础异常类,然后基于这个基础类为不同的错误情况创建不同的子类:
class Error(Exception): """Base class for exceptions in this module.""" pass class InputError(Error): """Exception raised for errors in the input. Attributes: expression -- input expression in which the error occurred message -- explanation of the error """ def __init__(self, expression, message): self.expression = expression self.message = message class TransitionError(Error): """Raised when an operation attempts a state transition that‘s not allowed. Attributes: previous -- state at beginning of transition next -- attempted new state message -- explanation of why the specific transition is not allowed """ def __init__(self, previous, next, message): self.previous = previous self.next = next self.message = message
大多数的异常的名字都以”Error”结尾,就跟标准的异常命名一样。
finally语句
try 语句还有另外一个可选的子句,这个语句无论在任何情况下都会执行,也就是所谓的最终执行块,这个代码块里的代码不管什么有没有发生异常都会被执行,一般用于执行close之类的关闭资源的语句。 例如:
try: filedata = open("E:/test.txt") except IOError: print("文件打开失败!") finally: filedata.close() print("资源已关闭!")
运行结果:
资源已关闭!
以上例子不管 try 子句里面有没有发生异常,finally 子句都会执行。
如果一个异常在 try 子句里(或者在 except 和 else 子句里)被抛出,而又没有任何的 except 把它截住,那么这个异常会在 finally 子句执行后再次被抛出。
代码示例:
try: filedata = open("E:/test.txt") raise ZeroDivisionError except IOError: print("文件打开失败!") finally: filedata.close() print("资源已关闭!")
运行结果:
资源已关闭! Traceback (most recent call last): File "E:/PythonProject/TestExcept.py", line 3, in <module> raise ZeroDivisionError ZeroDivisionError
预定义的清理行为
一些对象定义了标准的清理行为,无论系统是否成功的使用了它,一旦不需要它了,那么这个标准的清理行为就会执行。
这面这个例子展示了尝试打开一个文件,然后把内容打印到屏幕上:
for line in open("myfile.txt"): print(line, end="")
以上这段代码的问题是,当执行完毕后,文件会保持打开状态,并没有被关闭。
之前介绍过的关键词 with 语句就可以保证诸如文件之类的对象在使用完之后一定会正确的执行他的清理方法,这种就是预定义的清理行为:
with open("myfile.txt") as f: for line in f: print(line, end="")
以上这段代码执行完毕后,就算在处理过程中出问题了,文件 f 也会关闭。