我们在编程时常常要和各种错误信息打交道,当Python解释器发现程序的错误时,就会抛出“异常”(Exception)来提示错误——这种情况可能发生于“编译时”和“运行时”这两个不同的阶段:Python程序在运行之前要先编译,如果编译未通过就不会开始运行——你可以在IPython一次交互中输入包含多条语句的程序来验证一下(按Ctrl+Enter换行,按Shift+Enter提交):
In [1]: print(2/3)
...: print(2///3)
...: print("结束")
File "<ipython-input-1-90ecc1ce7c0b>", line 2
print(2///3)
^
SyntaxError: invalid syntax
In [2]: print(2/3)
...: print(2/0)
...: print("结束")
...:
0.6666666666666666
Traceback (most recent call last):
File "<ipython-input-2-7d58b37c849b>", line 2, in <module>
print(2/0)
ZeroDivisionError: division by zero
上面第一段程序的第二句不符合语法,编译因此中断并抛出“语法错误”(SyntaxError)异常,这就属于编译时错误;通过编译的程序在运行期间仍可能出现导致程序中止的问题——上面第二段程序在语法上没有问题,第一句也正常执行了,但第二句中除数为零的运算违反数学规则,运行因此中止并抛出“除零错误”(ZeroDivisionError)异常,这就属于运行时错误。
运行时错误难免会发生——用户输入的数据不完整、打开的文件格式不正确或连接的网络不通畅等等,都可能导致抛出异常。开发者应该预先考虑到各种异常情况,增加相应的代码来处理运行时错误以避免程序意外中止。所有异常对象都是特定异常类的实例,最基本的异常类是BaseException,通常在编程中需要处理的异常则都继承自BaseException的子类Exception。如果想要查看异常类的继承关系,可以使用mro方法返回“方法解析顺序”(Method Resolution Order)——这实际上就是类的继承顺序,一直上溯到object类为止。此外,你还可以使用raise语句直接“召唤”异常,或使用assert语句“指明”条件来触发异常。
In [3]: SyntaxError.mro()
Out[3]: [SyntaxError, Exception, BaseException, object]
In [4]: ZeroDivisionError.mro()
Out[4]: [ZeroDivisionError, ArithmeticError, Exception, BaseException, object]
In [5]: help(Exception.mro)
Help on built-in function mro:
mro(...) method of builtins.type instance
mro() -> list
return a type's method resolution order
In [6]: raise Exception("发生了错误")
Traceback (most recent call last):
File "<ipython-input-6-2b67d8d306dd>", line 1, in <module>
raise Exception("发生了错误")
Exception: 发生了错误
In [7]: a = 20
...: assert a <= 10, "数值过大"
...:
Traceback (most recent call last):
File "<ipython-input-7-58c21e2f5947>", line 2, in <module>
assert a <= 10, "数值过大"
AssertionError: 数值过大
Python提供try语句来实现异常处理:“尝试”执行可能出错的代码,如有“异常”就进行相应处理——提醒用户再次输入、检查文件或重新连网等等,使程序能够顺畅地运行下去。下面我们来看一个非常简单的计算程序:根据输入的算式输出答案——使用eval函数能把字符串作为表达式来求值,但是用户可能输入不合法的表达式,导致运行时错误的发生,因此就要使用try语句来处理异常:尝试运行try代码段,如无异常则运行之后的语句,如有异常就转而执行except代码段,这样即使用户输入错误的内容,程序也不至于崩溃。
"""calc.py 简单的计算程序
"""
ans = ""
while True:
ask = input("输入算式或回车退出:")
if ask == "":
break
try:
ans = eval(ask)
except:
pass
print(ans)
以上程序中的pass语句表示什么也不做直接放过,这当然不好——正如Python之禅所言“错误不可放过”——以下程序捕获抛出的Exception类(包括其所有继承者)实例并赋值给变量e,这样就能用repr(e)返回异常类型及提示信息,通知我们具体发生了什么问题再继续运行:
except Exception as e:
ans = repr(e)
你可以在try语句中使用多个except子句,捕获不同类型的异常进行分别处理,例如输出自定义的提示信息。此外,你还可以添加一个finally子句,在其中编写无论是否发生异常都要“最终”执行的代码。
except SyntaxError:
ans = "语法不正确"
except ZeroDivisionError:
ans = "除数不能为零"
except Exception as e:
ans = "发生{}错误".format(e.__class__.__name__)
finally:
print("输出结果:", end="")
下面让我们来看一个在线随机图片API的测试程序:访问指定的“岁月小筑”网址会返回一张随机图片的网址,然后调用默认浏览器打开:
"""urlgetpic.py “岁月小筑”随机图片API测试程序
获取一张随机图片的网址并用浏览器打开
"""
from urllib.request import urlopen
import webbrowser
url = "http://img.xjh.me/random_img.php?return=url"
def main():
try:
pic = urlopen(url).read().decode()
webbrowser.open("http:" + pic)
except Exception as e:
print(repr(e))
if __name__ == "__main__":
main()
网络总会有连不上的时候,所以访问在线资源的代码应该用try语句加以保护。
——编程原来这样……
编程小提示:开放API
API指“应用编程接口”(Application Programming Interface),其作用是让外部开发者可以调用程序的特定功能,而又无需关心程序内部的细节。在互联网时代,把网络服务封装成一系列数据接口开放出去供第三方开发者使用,这就叫做开放API——从更广泛的意义上讲,任何网站都是API,你可以编写程序从网站中获取所需要的信息,就像调用本地函数返回结果一样。