错误处理
调试
新增:2020-01-19 增加关于文件读取的方法tell(), seek()等知识。
错误处理
高级语言都会使用内置的一套try...except...finally...的错误处理机制, 可以更高效的处理错误,
无需程序员自己写错误处理的代码。
try
try:print('try...')
r= 10 / int('2')print('result:', r)exceptValueError as e:print('ValueError:', e)exceptZeroDivisionError as e:print('ZeroDivisionError:', e)else:print('no error!')finally:print('finally...')print('END')
如果有错误,根据发生不同类型的错误,使用不同的except处理。
int('a')会出发 ValueError
10/0会触发ZeroDivisionError。
如果没有找到expect匹配的异常,则将异常传递到外部的try语句中。会显示traceback
否则代表没有错误,则执行else。
无论是否发生异常,最后都要执行finally
常见的错误类型和继承关系
看文档Built-in Exceptions
try的好处
可以处理try子句中调用(间接调用)的函数内部发生的异常,即跨多层调用。
函数main()调用bar(), bar调用foo(), 只要期间发生错误,try就会处理。
deffoo(s):return 10 /int(s)defbar(s):return foo(s) * 2
defmain():try:
bar('0')exceptException as e:print('Error:', e)finally:print('finally...')
调用栈
如果错误没有被捕获,它就会一直往上抛,最后被Python解释器捕获,打印一个错误信息,然后程序终止。
错误信息是Traceback (most recentcalllast)...。它是一个错误路径。可以在最后查看错误原因,定位错误位置。
例子:
importsystry:
f= open('example.log', 'r+')
s=f.readline()
i=int(s.stripd())exceptOSError as err:print("OS error: {}".format(err))exceptValueError:print("Could not convert datga to a integer")except:print("Unexpected error", sys.exc_info()[0])raise
else:print(i)#OSError类, 就是IOError的别名。输入输出错误类.会升起和系统错误代码香港的错误。包括很多子类
#最后的excepte没有指定异常类,当作通配符用。#sys.exc_info会返回,当前正在处理的exception的信息:#返回的值是一个tuple:包括3个value。(type, value, traceback), 如果没有对应的值返回None。
#example.log
213
1
解释:
f = open(), 使用内建方法open。open其实是io模块的方法。
s = f.readline(), 会把example.log文件的第一行代码返回给变量s。
s.stripd(),这是❌的拼写,多写了一个字面d,系统有内置函数strip(),所以要去掉字母d,否则会执行exept子句。
代码最后一个except子句没有异常类,所以向上级抛错误。
sys.exc_info():这个方法是sys模块的方法。用法见注释。
本例子返回的是:
Unexpected error Traceback (most recent call last):
File"linshi.py", line 6, in i=int(s.stripd())
AttributeError:'str' object has no attribute 'stripd'
except子句的as
except Exception as inst:
as 后面的inst变量和产生的异常类的实例绑定,如果异常实例有参数,使用inst.args查看。
因为异常类BaseException类定义了object.__str__(self)方法, 这个方法返回对象自身的字符串格式,所以可以直接使用inst,查看错误。
例子:
defthis_fails():
x= 1/0try:
this_fails()exceptZeroDivisionError as err:print("Handing run-time error:", err.args)print("Handing run-time error:", err)#返回:
Handing run-time error: ('division by zero',)
Handing run-time error: division by zero
logging模块
可以把错误信息记录到日志。并让程序继续执行。
#err_logging.py
importloggingdeffoo(s):return 10 /int(s)defbar(s):return foo(s) * 2
defmain():try:
bar('0')exceptException as e:
logging.exception(e)
main()print('END')
抛出异常 raise语句
raise(参数), 参数是异常类或者它的实例。如果参数是异常类,它会通过调用构造函数来暗中/隐式实例化。
内置函数有各种类型的错误。也可以自己编写函数,然后抛出错误。
#err_raise.py
classFooError(ValueError):pass
deffoo(s):
n=int(s)if n==0:raise FooError('invalid value: %s' %s)return 10 /n
foo('0')
编写一个错误类FooError。
用raise语句,生成FooError的实例。
尽量使用内置的函数。如ValueError, TypeError
最常用的错误处理方式:try...except...并调用一个单独的raise。
目的:
用except捕获指定❌
然后,用raise语句进行trackback。
下面代码:注释掉了raise,所以最后结果不会显示Traceback (most recent call last)的信息。
from functools importreducedefstr2num(s):returnint(s) #改为float(s)即可纠正错误defcalc(exp):
ss= exp.split('+')
ns=map(str2num, ss)return reduce(lambda acc, x: acc +x, ns) #functools的方法reduce(function, iterable)defmain():try:
r= calc('99 + 88 + 7.6')print('99 + 88 + 7.6 =', r)exceptValueError as e:print(">>>%s" %e)#raise
main()
加上单独的raise的作用,就是把当前错误原样抛出,可进行后续的追踪。
由于当前函数不知道应该怎么处理该错误,所以,继续往上抛错误,让顶层调用者去处理。
如果raise带了不同的异常类实例的参数,,可以把一种类型的错误转化为另一种类型:
try:10 /0exceptZeroDivisionError:raise ValueError('input error!')
finally的作用
finally是try语句的可选子句,它不论是否产生异常都会执行。用于定义清理操作。
但有几点特殊情况,执行的时间不一样。具体可见文档: 8.6. 定义清理操作
调试
第一种方法简单直接粗暴有效,就是用print()把可能有问题的变量打印出来看看。
第二种:print()但看完还要删除,因此可以用assert。
不用了,可以在启动时,带上参数-O关掉assert的功能。
#err.py
deffoo(s):
n=int(s)assert n != 0, 'n is zero!'
return 10 /ndefmain():
foo('0')
main()
$ python -O err.py
注意是大写的字母O,
第三种logging。
importlogging
logging.basicConfig(level=logging.INFO) #进行配置,logging级别,默认是WARNING,所以INFO,DEBUG级别的不会被追踪
s= '0'n=int(s)
logging.info('n= %d' %n)print(10/0)
这就是logging的好处,它允许你指定记录信息的级别。这样一来,你可以放心地输出不同级别的信息,也不用删除,最后统一控制输出哪个级别的信息。
级别何时使用
DEBUG
细节信息,仅当诊断问题时适用。
INFO
确认程序按预期运行
WARNING
表明有已经或即将发生的意外(例如:磁盘空间不足)。程序仍按预期进行
ERROR
由于严重的问题,程序的某些功能已经不能正常执行
CRITICAL
严重的错误,表明程序已不能继续执行
默认的级别是``WARNING``,意味着只会追踪该级别及以上的事件,除非更改日志配置。
追踪的事件可以:
简单的:输出到控制台
复杂的:写入磁盘文件。
记录日志到磁盘文件:
使用basicConfig()的配置函数,更改上面的代码:
logging.basicConfig(filename='example.log',level=logging.DEBUG)
这样,会在当前文件夹下生成一个example.log文件,用于记录日志信息。就是logging.info()输出的信息。
在消息中显示时间/日期
日志很重要的是,可以在之后查看,所以要加上日期时间:
logging.basicConfig(filename='example.log',format="%(levelname)s: %(asctime)s %(message)s",level=logging.INFO) #进行配置
加上参数:format="%(levelname)s: %(asctime)s %(message)s"
会在日志显示:
WARNING: 2019-11-16 19:29:46,496 n= 0
是否要深入学习?
如果日志需求很简单,上面的就足够了,否则可以看进阶日志教程和操作手册。
第四种pdb, Python自带的调试器
$ python -m pdb err.py> /Users/michael/Github/learn-python3/samples/debug/err.py(2)()-> s = '0'
带上参数-m pdb
运行时,c是continue下一个块, n是next即下一行,q是退出。
如果想要设置一个中断点,在代码中加上pdb.set_trace()方法即可,运行是到这行代码会暂停并进入调试程序。
⚠️类似Ruby/Rails的byebug。中断点是byebug, 启动脚本: byebug xxx.rb
IDE: 集成开发环境
如果要比较爽地设置断点、单步执行,就需要一个支持调试功能的IDE。目前比较好的Python IDE有:
Visual Studio Code:https://code.visualstudio.com/,需要安装Python插件。
另外,Eclipse加上pydev插件也可以调试Python程序。
虽然用IDE调试起来比较方便,但是最后你会发现,logging才是终极武器。
文件的定位读写
f = open("123.txt", "w+")
f.write("hello world")
content = f.read()
print(content) # 为什么打印不出内容?
这是因为有一个关于打印指针的概念。
第3行的变量content其实是""空字符串,因为文件中的指针当前指向的是文本的最后。
所以,如果希望打印第2行输入的字符,需要调整文件指针的位置。这里涉及2个方法:
tell(),判断当前指针的位置
seek(offset, from) 移动指针
seek(offset, from)有2个参数
offset:偏移量。正整数向右偏移,负数向左偏移。
from:方向
0:表示文件开头
1:表示指针当前的位置
2:表示文件末尾