文章目录
使用场景
写一个函数两分钟,找bug可能两小时?那么我们可以采用调试程序的手段来修复bug。
方法一:print方法列出所有存疑变量
将所有有可能存在bug的变量打印出来。
#! /usr/bin/python3
# coding=utf-8
def foo(s):
n = int(s)
print('n = %d'%n)
return 10/n
def main():
foo('0')
main()
输出结果
n = 0
raceback (most recent call last):
File “test.py”, line 9, in
main()
File “test.py”, line 8, in main
foo(‘0’)
File “test.py”, line 6, in foo
return 10/n
ZeroDivisionError: division by zero
这种方法存在的问题就是,如果bug很多,那么遍地print。
方法二:assert方法
#! /usr/bin/python3
# coding=utf-8
def foo(s):
n = int(s)
assert n != 0, 'n is zero!'
return 10/n
def main():
foo('0')
main()
上述assert
的意思是,表达式n != 0
应该是True
,否则,后面的代码就会出错。
Traceback (most recent call last):
File "test.py", line 9, in <module>
main()
File "test.py", line 8, in main
foo('0')
File "test.py", line 5, in foo
assert n != 0, 'n is zero!'
AssertionError: n is zero!
如果assert失败,那么assert
本身就会抛出一个assertionError.
但是如果assert很多,那么这种方法与print也没什么区别,可以用-O
(大写字母O)来关掉assert。
python -O test.py
输出结果
Traceback (most recent call last):
File “test.py”, line 9, in
main()
File “test.py”, line 8, in main
foo(‘0’)
File “test.py”, line 5, in foo
assert n != 0, ‘n is zero!’
AssertionError: n is zero!
关闭后的assert
可以当做pass
。
方法三、pdb
Python debugger,专门调试Python程序的官方库。
单点调试
测试文件:test.py
#! /usr/bin/python3
# coding=utf-8
s = '0'
n = int(s)
print(10/n)
启动:python3 -m pdb test.py
,输入l
(小写字母l)可以查看代码:
输入n
(小写字母n)可以单步执行代码。任何时候都可以输入p 变量名
来查看变量。输入命令q
来结束调试,退出程序。
pdb.set_trace() 设置断点
测试文件:test.py
#! /usr/bin/python3
# coding=utf-8
import pdb
s = '0'
n = int(s)
pdb.set_trace()
print(10/n)
运行代码python3 test.py
,程序会自动在pdb.set_trace()暂停并进入pdb调试环境,可以用命令p
查看变量,或者用命令c
继续运行:
此处变量n的值为0,故当继续运行时程序输出zeroDivisionError并终止。
这种方法比直接启动pdb调试要稍微快一点,但是也不是特别快。
方法四、logging(推荐)
概念
日志是一种可以追踪某些软件或程序运行时所发生事件的方法。软件开发人员可以向他们的代码中调用日志记录相关的方法来表明发生了某些事情。一个事件可以用一个可包含可选变量数据的消息来描述。此外,事件也有重要性的概念,这个重要性也可以被称为严重性级别(level)
作用
- 程序调试
- 了解软件程序运行情况,是否正常
- 软件程序运行故障分析与问题定位
将print替换为logging,该方法不会抛出错误而且可以输出到文件。
日志级别
等级(level) | 描述 | 严重级别 |
---|---|---|
DEBUG | 最详细的日志信息,典型应用场景是 问题诊断 | 1(最低) |
INFO | 信息详细程度仅次于DEBUG,通常只记录关键节点信息,用于确认一切都是按照我们预期的那样进行工作 | 2 |
WARNING | 当某些不期望的事情发生时记录的信息(如,磁盘可用空间较低),但是此时应用程序还是正常运行的 | 3 |
ERROR | 由于一个更严重的问题导致某些功能不能正常运行时记录的信息 | 4 |
CRITICAL | 当发生严重错误,导致应用程序不能继续运行时记录的信息 | 5(最高) |
使用方法
使用logging提供的模块级别的函数
模块级别的日志记录函数也是对logging日志系统相关类的封装而已。
函数 | 说明 |
---|---|
logging.debug(msg, *args, **kwargs) | 创建一条严重级别为DEBUG的日志记录 |
logging.info(msg, *args, **kwargs) | 创建一条严重级别为INFO的日志记录 |
logging.warning(msg, *args, **kwargs) | 创建一条严重级别为WARNING的日志记录 |
logging.error(msg, *args, **kwargs) | 创建一条严重级别为ERROR的日志记录 |
logging.critical(msg, *args, **kwargs) | 创建一条严重级别为CRITICAL的日志记录 |
logging.log(level, *args, **kwargs) | 创建一条严重级别为level的日志记录 |
logging.basicConfig(**kwargs) | 对root logger进行一次性配置 |
其中logging.basicConfig(**kwargs)函数用于指定“要记录的日志级别”、“日志格式”、“日志输出位置”、“日志文件的打开模式”等信息,其他几个都是用于记录各个级别日志的函数。
logging.basicConfig()参数名称 | 描述 |
---|---|
filename | 指定日志输出目标文件的文件名,该参数指定后输出信息不会出现在控制台上 |
filemode | 指定日志文件的打开模式,默认为’a’。需要注意的是,该选项要在filename指定时才有效 |
format | 指定日志格式字符串,即指定日志输出时所包含的字段信息以及它们的顺序。 |
datefmt | 指定日期/时间格式。需要注意的是,该选项要在format中包含时间字段%(asctime)s时才有效 |
level | 指定日志器的日志级别 |
stream | 指定日志输出目标stream,如sys.stdout、sys.stderr以及网络stream。需要说明的是,stream和filename不能同时提供,否则会引发 ValueError异常 |
style | Python 3.2中新添加的配置项。指定format格式字符串的风格,可取值为’%’、’{‘和’$’,默认为’%’ |
handlers | Python 3.3中新添加的配置项。该选项如果被指定,它应该是一个创建了多个Handler的可迭代对象,这些handler将会被添加到root logger。需要说明的是:filename、stream和handlers这三个配置项只能有一个存在,不能同时出现2个或3个,否则会引发ValueError异常。 |
#!/usr/bin/python3
# coding=utf-8
import logging
logging.debug("debug")
logging.info('info')
logging.warning('warning')
logging.error('error')
logging.critical('critical')
输出结果
WARNING:root:warning
ERROR:root:error
CRITICAL:root:critical
上述内容分别是日志级别:
日志器名称:
日志内容
,因为logging模块提供的日志记录函数所使用的日志器设置的日志格式默认是BASIC_FORMAT,其值为:"%(levelname)s:%(name)s:%(message)s"
另外,logging模块提供的日志记录函数所使用的日志器设置的日志级别是WARNING,因此只有WARNING级别的日志记录以及大于它的ERROR和CRITICAL级别的日志记录被输出了,而小于它的DEBUG和INFO级别的日志记录被丢弃了。
#!/usr/bin/python3
# coding=utf-8
import logging
# 日志格式:时间-日志级别-日志信息
LOG_FORMAT = "%(asctime)s - %(levelname)s - %(message)s"
# 日志配置信息:日志名,日志级别,日志格式(上面定义的)
logging.basicConfig(filename='my.log', level=logging.DEBUG, format=LOG_FORMAT)
logging.debug("debug log.")
logging.info("info log.")
logging.warning("warning log.")
logging.error("error log.")
logging.critical("critical log.")
输出结果:在当前目录下使用cat my.log
或是用其他工具打开log文件查看日志信息
在前文的基础上,我们进一步修改日期格式:
#!/usr/bin/python3
# coding=utf-8
import logging
# 日志格式:时间-日志级别-日志信息
LOG_FORMAT = "%(asctime)s - %(levelname)s - %(message)s"
DATE_FORMATE = "%m/%d/%Y %H:%M:%S %p"
# 日志配置信息:日志名,日志级别,日志格式(上面定义的)
logging.basicConfig(filename='my.log', level=logging.DEBUG, format=LOG_FORMAT, datefmt=DATE_FORMATE)
logging.debug("debug log.")
logging.info("info log.")
logging.warning("warning log.")
logging.error("error log.")
logging.critical("critical log.")
然后运行程序并查看日志内容:
2019-07-27 22:37:49,802 - DEBUG - debug log.
2019-07-27 22:37:49,802 - INFO - info log.
2019-07-27 22:37:49,802 - WARNING - warning log.
2019-07-27 22:37:49,802 - ERROR - error log.
2019-07-27 22:37:49,802 - CRITICAL - critical log.
07/27/2019 22:41:57 PM - DEBUG - debug log.
07/27/2019 22:41:57 PM - INFO - info log.
07/27/2019 22:41:57 PM - WARNING - warning log.
07/27/2019 22:41:57 PM - ERROR - error log.
07/27/2019 22:41:57 PM - CRITICAL - critical log.
可以看到上述内容包括了原始日志内容和修改了日期格式之后的内容,并不是简单的覆盖前面的日志而是在文件后面追加。
使用Logging日志系统的四大组件
- logging模块的四大组件简介
组件名 | 对应类名 | 说明 | 方法 |
---|---|---|---|
日志器 | loggers | 提供应用程序代码直接使用的接口 | 配置方法和消息发送方法 |
控制器 | handlers | 用于将日志记录发送到指定的目的位置 | |
过滤器 | filters | 提供更细粒度的日志过滤功能,用于决定哪些日志记录将会被输出(其它的日志记录将会被忽略) | |
格式器 | formatters | 用于控制日志信息的最终输出格式 |
说明: logging模块提供的模块级别的那些函数实际上也是通过这几个组件的相关实现类来记录日志的,只是在创建这些类的实例时设置了一些默认值。
- 日志器(logger)需要通过处理器(handler)将日志信息输出到目标位置,如:文件、sys.stdout、网络等;
- 不同的处理器(handler)可以将日志输出到不同的位置;
- 日志器(logger)可以设置多个处理器(handler)将同一条日志记录输出到不同的位置;
每个处理器(handler)都可以设置自己的过滤器(filter)实现日志过滤,从而只保留感兴趣的日志; - 每个处理器(handler)都可以设置自己的格式器(formatter)实现同一条日志以不同的格式输出到不同的地方。
简单点说就是:日志器(logger)是入口,真正干活儿的是处理器(handler),处理器(handler)还可以通过过滤器(filter)和格式器(formatter)对要输出的日志内容做过滤和格式化等处理操作。
- logging日志模块相关类及其常用方法介绍
logger类对象任务:
- 向应用程序暴露几个方法,使应用程序可以在运行时记录日志消息;
- 基于日志严重等级或filter对象阿里决定要对那些日志进行后续处理;
- 将日志消息传递给所有感兴趣的日志的handlers
常用配置方法:
方法名 | 方法描述 |
---|---|
logger.setLevel() | 设置日志处理器将处理的最低严重级别。 |
logger.addHandlers()和logger.removeHandler() | 为该logger对象添加和移除handler对象 |
logger.addFilter()和logger.removeFilter() | 为该logger对象添加和移除一个Filter |
如果函数参数为Warning,那么低于这个级别的日志消息将被忽略/丢弃,logger只会处理Warning、error和critical级别的日志。
logger对象配置完成之后,可以使用下面的方法来创建日志记录:
方法 | 描述 |
---|---|
logger.debug(),logger.info(),logger.warning(),logger.error(),logger.critical() | 创建一个与他们的方法名对应等级的日志记录。 |
logger.exception() | 创建一个类似于logger.error()的日志记录。 |
logger.log() | 需要获取一个明确的日志level参数来创建一个日志记录 |
- exception和error的区别在于exception会输出堆栈追踪信息;通常只在一个exception handler中调用该方法。
- logger.log()与logger.debug()等方法相比虽然需要多传一个level参数,当需要记录自定义level的日志时需要改方法完成。
获取logger对象的方法,logging.logger()方法:
日至期名称name为可选参数,默认名称为root,若以相同的name参数值多次调用getLogger()方法,将会返回指向同一个logger对象的引用。
handler类对象的作用和任务:
作用:基于日志消息的level将消息发送到handler指定的位置(文件、网络、邮件等)。logger对象可以通过addHandler()方法为自己添加若干个handler对象。
任务示例: - 把所有日志都发送到一个日志文件中;
- 把所有严重级别大于等于某个等级的日志发送到stdout(标准输出);
- 把所有严重级别为critical的日志发送到一个email地址。
上述几个场景就需要3个不同的handler,每个handler负责发送一个特定级别的日志到特定位置。
配置方法:
方法名 | 描述 |
---|---|
handler.setLevel() | 设置handler将会处理的最低级别的日志消息 |
handler.Formatter() | 为handler设置一个格式器对象 |
handler.addFilter()和handler.removeFilter() | 为handler添加和删除一个过滤器对象 |
特别说明:应用程序代码不应该直接实例化和使用handler实例。handler只是一个基类。它定义了所有handler的基本接口,同时提供了一些子类可以直接使用或是覆盖的默认行为。
Formatter类
formatter对象用于配置日志信息的最终顺序、结构和内容。
logging.handler()基类的应用代码可以直接实例化formatter类,如果需要特殊的处理行为,也可以实现一个formatter的类来完成。
logging.Formatter.__init__(fmt=None, datefmt=None, style='%')
上述构造方法接收3个可选参数:
- fmt:接受消息格式化字符串,如果不指定该参数则默认使用message的默认值
- datefmt:指定日期格式字符串,如果不指定则使用默认的
%Y-%m-%d %H:%M:%S
格式 - style:可取的值为’%‘,’{‘,’$‘,如如果不指定则为默认的%。
Filter类
filter可以被handler和logger用来做比level更细粒度、更复杂的过滤功能。类的定义如下:
class logging.Filter(name='')
filter(record)
- 配置logging的几种方式
- 使用Python代码显示创建loggers,handlers,formatters并分别调用他们的配置函数;
- 创建一个日志配置文件,然后使用
fileConfig()
函数来读取该文件的内容; - 创建一个包含配置信息的dict,然后将其传递给
dictConfig()
函数
小结
调试方式对比总结
日志调试为荣,单点调试为耻。
logging
的优势在于允许你指定记录信息的级别,有debug
,info
,warning
,error
等几个级别,当我们指定level=INFO
时,logging.debug
就不起作用了。同理,指定level=WARNING
后,debug
和info
就不起作用了。这样一来,你可以放心地输出不同级别的信息,也不用删除,最后统一控制输出哪个级别的信息。
logging
的另一个好处是通过简单的配置,一条语句可以同时输出到不同的地方,比如console和文件。
logging模块参考文档
安利deep tabnine
tabnine
发现一个神奇的操作,在我输入某些单词的时候,Python会自动把整个句子补全,猜测是deep tabNine
(vscode的一个插件)的功能。比如我要导入pdb的时候,当我输入到import pd在tab的时候,自动帮我补全了set_trace()整个句子。效果类似于下面:
import import pdb; pdb.set_trace()
所以,以后像这类高频的句子,只需要输入pdb三个字母就可以了,同理还有如下句子,只需要输入main这个词再tab就可以了
if __name__ == "__main__":
pass
咋说呢,笑着安利吧。