在前面介绍Python语法的过程中,我们已经接触到了解释器给的错误和异常,但并没有详细讲解它们。现在我们就全面的来学习Python是对语法错误等错误进行定义和处理的,这包括至少有两种可以区分的错误,它们是语法错误和异常。
语法错误
Python的语法错误就是不符合Python语法的错误,又称为解析错误。这种错误是初学Python对语法不是很熟悉时经常犯的。比如下面的例子:
In [1]: if 2 == 3 print('imposible')
File "", line 1
if 2 == 3 print('imposible')
^
SyntaxError: invalid syntax
解释器在解释上面的代码时,就会发现表达式2 == 3后面少了一个冒号:,这时它就会报出一个错误SyntaxError: invalid syntax,并且输出出现语法错误的那一行,并显示一个“箭头”,指向这行里面检测到第一个错误。 错误是由箭头指示的位置上面的 token 引起的(或者至少是在这里被检测出的)。文件名和行号也会被输出,以便输入来自脚本文件时你能知道去哪检查。
解释器这样报出的好处是:
(1)告诉我们哪一行代码出错了;
(2)错误的类型是什么。
这样非常有利于我们排除错误,修正程序。
异常(Exception)
如果我们对语法很熟悉,写出来的代码在语法上都是正确的,但也不能保证在执行时程序不会引发错误。在执行时检测到的错误被称为异常,异常不一定会导致严重后果,但我们不在代码中对它们进行处理,就可能会导致程序中断执行。下面是一些常见的错误异常信息:
In [2]: 5 / 0
----------------------------
ZeroDivisionError Traceback (most recent call last)
in
----> 1 5 / 0
ZeroDivisionError: division by zero
In [3]: a + 3
------------------------------
NameError Traceback (most recent call last)
in
----> 1 a + 3
NameError: name 'a' is not defined
In [4]: 10 + '1'
--------------------------------
TypeError Traceback (most recent call last)
in
----> 1 10 + '1'
TypeError: unsupported operand type(s) for +: 'int' and 'str'
我们看到,异常有不同的类型,其类型名称会作为错误信息的一部分中打印出来,上述示例中的异常类型分别是:ZeroDivisionError,NameError和TypeError。对于所有内置异常,打印出来的字符串是内置异常的名称。对于用户定义的异常则不一定如此,但我们自定义异常时最好按照内置异常那样去定义,这是一个很有用的规范。标准的异常类型是内置的标识符,而不是保留关键字。
打印出来的异常名称后面是异常发生的原因。错误信息的前一部分以堆栈回溯的形式显示发生异常时代码的上下文。一般它包含列出源代码行的堆栈回溯;但是它不会显示从标准输入中读取的行。
Python内置了很多异常,它们都从BaseException继承而来,下面是内置异常的继承关系:
异常处理
既然程序会抛出异常,那我们就可以编写代码处理这些异常。先看下面的例子,它会让用户一直输入,直到输入的是一个有效的整数。我们也可以使用Control-C来中断程序;这个Control-C引起的中断会引发 KeyboardInterrupt 异常。
In [6]: while 1:
...: try:
...: n = int(input('input a number:'))
...: print('You typed number:', n)
...: break
...: except ValueError:
...: print('Nooo! It is not a number, Try agin')
...:
input a number:a
Nooo! It is not a number, Try agin
input a number:b
Nooo! It is not a number, Try agin
input a number:3
You typed number: 3
当我们输入a时,它不能转换成整数就会报错异常ValueError。转换为整数的那条语句报出了异常,它后面的语句就不再执行,而是跳到except那里去执行它里面的语句。
try语句的工作原理如下:
首先,执行try 子句,即try和except关键词之间的(一行或多行)语句;
如果没有发生异常,则跳过except子句并完成try子句的执行;
如果执行try子句是发生了异常,则跳过该子句的剩下部分。然后,去匹配异常的类型和except关键字后面的异常,如果异常类型匹配则执行except子句,之后继续执行try语句后面的代码。
如果发生的异常和except后面的异常不匹配,则将其传递到外部的try语句,如果没有找到处理代码,则它是一个未处理异常,执行将停止并显示错误信息。
一个try语句可以有多个except子句,以便不同的异常用不同的处理程序进行处理。每次遇到异常最多会执行一个except子句,也就是说,处理程序只处理相应的异常,而不处理同一try语句内其它处理程序的异常。但是,一个except子句可以将多个异常包含在一个元组内,例如:
try:
...
except (RuntimeError, TypeError, NameError):
pass
异常都是继承于BaseException,如果except子句中的类和发生的异常是同一个类,或者是异常的基类(父类),则异常和except子句中的类是兼容的。但是,反过来则不成立。我们看看下面的代码,它将一次打印B,C,D。
class B(Exception):
pass
class C(B):
pass
class D(C):
pass
for cls in [B, C, D]:
try:
raise cls()
except D:
print("D")
except C:
print("C")
except B:
print("B")
如果我把 except 子句颠倒过来,把 except B 放到第一个,猜猜它将会打印出什么?答案是它将打印 B,B,B。也就是第一个匹配的 except 子句被触发,因为B是C、D的父类。
最后的 except 子句可以省略异常名称,以用作通配符匹配所有的异常。这个要小心使用,因为这种方式很容易掩盖真正的编程错误!但是它可用于打印错误消息,然后重新引发异常(同样允许调用者处理异常):
import sys
try:
f = open('zzz.txt')
s = f.readline()
i = int(s.strip())
except OSError as err:
print("OS error: {0}".format(err))
except ValueError:
print("Could not convert string to integer.")
except:
print("Unexpected error:", sys.exc_info()[0])
raise
try 语句有一个可选的 else 子句,在使用时它必须放在所有的 except 子句后面。对于在try 子句不引发异常时必须执行的代码来说很有用。例如:
try:
f = open('zzz.txt', 'r')
except OSError:
print('cannot open', 'zzz.txt')
else:
print('zzz.txt', 'has', len(f.readlines()), 'lines')
f.close()
使用else子句的好处是,它避免了意外捕获由else子句引发的异常。也就是说,程序中我们只想捕获open引发的异常,而不捕获f.readlines()引发的错误。
异常在抛出时可能具有关联的值,称为异常参数。参数的存在和类型取决于异常类型。
except子句可以在异常名称后面指定一个变量,这个变量就是该异常的实例,它的参数存储在instance.args中。为了方便起见,异常实例定义了__str__(),因此可以直接打印参数而无需引用.args。也可以在抛出之前首先实例化异常,并根据需要向其添加任何属性。:
In [7]: try:
...: raise Exception('猿人学', 'Python')
...: except Exception as e:
...: print(type(e))
...: print(e.args)
...: print(e)
...: a, b = e.args
...: print('a =', a)
...: print('b =', b)
...:
('猿人学', 'Python')
('猿人学', 'Python')
a = 猿人学
b = Python
异常处理程序不仅可以处理try子句中遇到的异常,还可以处理try子句中调用的函数的内部发生的异常,例如:
In [8]: def func():
...: return 10/0
...:
In [9]: try:
...: func()
...: except ZeroDivisionError as err:
...: print('run-time error:', err)
...:
run-time error: division by zero
我的公众号:猿人学 Python 上会分享更多心得体会,敬请关注。
***版权申明:若没有特殊说明,文章皆是猿人学 原创,没有猿人学授权,请勿以任何形式转载。***