摘录:https://www.liaoxuefeng.com/wiki/1016959663602400/1017598873256736
- 错误处理
- 调试
- 新增:2020-01-19 增加关于文件读取的方法tell(), seek()等知识。
错误处理
高级语言都会使用内置的一套try...except...finally...
的错误处理机制, 可以更高效的处理错误,
无需程序员自己写错误处理的代码。
try
try: print('try...') r = 10 / int('2') print('result:', r) except ValueError as e: print('ValueError:', e) except ZeroDivisionError 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
常见的错误类型和继承关系
https://docs.python.org/3/library/exceptions.html#exception-hierarchy
try的好处
可以处理try子句中调用(间接调用)的函数内部发生的异常,即跨多层调用。
函数main()调用bar(), bar调用foo(), 只要期间发生错误,try就会处理。
def foo(s): return 10 / int(s) def bar(s): return foo(s) * 2 def main(): try: bar('0') except Exception as e: print('Error:', e) finally: print('finally...')
调用栈
如果错误没有被捕获,它就会一直往上抛,最后被Python解释器捕获,打印一个错误信息,然后程序终止。
错误信息是Traceback (most recent call last)...。它是一个错误路径。可以在最后查看错误原因,定位错误位置。
例子:
import sys try: f = open('example.log', 'r+') s = f.readline() i = int(s.stripd()) except OSError as err: print("OS error: {}".format(err)) except ValueError: 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 <class 'AttributeError'> Traceback (most recent call last): File "linshi.py", line 6, in <module> 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,查看错误。
例子:
def this_fails(): x = 1/0 try: this_fails() except ZeroDivisionError 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 import logging def foo(s): return 10 / int(s) def bar(s): return foo(s) * 2 def main(): try: bar('0') except Exception as e: logging.exception(e) main() print('END')
抛出异常 raise语句
raise(参数), 参数是异常类或者它的实例。如果参数是异常类,它会通过调用构造函数来暗中/隐式实例化。
内置函数有各种类型的错误。也可以自己编写函数,然后抛出错误。
# err_raise.py class FooError(ValueError): pass def foo(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 import reduce def str2num(s): return int(s) #改为float(s)即可纠正错误 def calc(exp): ss = exp.split('+') ns = map(str2num, ss) return reduce(lambda acc, x: acc + x, ns) #functools的方法reduce(function, iterable) def main(): try: r = calc('99 + 88 + 7.6') print('99 + 88 + 7.6 =', r) except ValueError as e: print(">>>%s" % e) # raise main()
- 加上单独的raise的作用,就是把当前错误原样抛出,可进行后续的追踪。
- 由于当前函数不知道应该怎么处理该错误,所以,继续往上抛错误,让顶层调用者去处理。
- 如果raise带了不同的异常类实例的参数,,可以把一种类型的错误转化为另一种类型:
try: 10 / 0 except ZeroDivisionError: raise ValueError('input error!')
finally的作用
finally是try语句的可选子句,它不论是否产生异常都会执行。用于定义清理操作。
但有几点特殊情况,执行的时间不一样。具体可见文档: 8.6. 定义清理操作
调试
第一种方法简单直接粗暴有效,就是用print()
把可能有问题的变量打印出来看看。
第二种:print()但看完还要删除,因此可以用assert。
不用了,可以在启动时,带上参数-O关掉assert的功能。
#err.py def foo(s): n = int(s) assert n != 0, 'n is zero!' return 10 / n def main(): foo('0') main()
$ python -O err.py
注意是大写的字母O,
第三种logging。
import logging logging.basicConfig(level=logging.INFO) #进行配置,logging级别,默认是WARNING,所以INFO,DEBUG级别的不会被追踪 s = '0' n = int(s) logging.info('n= %d' % n) print(10/0)
这就是logging
的好处,它允许你指定记录信息的级别。这样一来,你可以放心地输出不同级别的信息,也不用删除,最后统一控制输出哪个级别的信息。
级别 |
何时使用 |
---|---|
|
细节信息,仅当诊断问题时适用。 |
|
确认程序按预期运行 |
|
表明有已经或即将发生的意外(例如:磁盘空间不足)。程序仍按预期进行 |
|
由于严重的问题,程序的某些功能已经不能正常执行 |
|
严重的错误,表明程序已不能继续执行 |
默认的级别是``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)<module>() -> 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插件。
PyCharm:http://www.jetbrains.com/pycharm/
另外,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:表示文件末尾