文章目录
- Python-调试(各种方式)
- print调试
- Icecream调试
- PySnooper
- assert(断言)
- pdb(单步调试)
- Linux环境可视化调试pudb
- 日志logging
Python-调试(各种方式)
程序编写完成或在编写过程中,需要对程序进行测试,根据测试发现的错误,进一步诊断,找出发生错误的原因和具体代码位置进行修改,这个过程称为程序调试。在一些情况下,可能需要查看或跟踪程序的运行状态,这种情况也属于程序调试。
print调试
调试最简单的方式就是打印输出,而print函数就可以输出各种类型变量,配合着格式化输出,我们可以打印出程序运行过程中各个变量的状态值。
if __name__ == '__main__':
a=1
print(f'值:{a}') # 值:1
使用这种方式的好处是我们不需要引入其它包,我们只需要使用简单的print就可以调试我们的程序,当然,它的缺点也很明显,有时候为了调试一些变量,我们不得不写很多print语句,而且有时候为了更优雅地显示数据,我们不得不写很多代码。
Icecream调试
和print调试类似但是信息更多,可以输出时间,文件,和打印的位置,并且可以统一关闭和启动
分别使用 ic.disable() 和 ic.enable() 。
from icecream import ic
from datetime import datetime
ic.configureOutput(prefix=f'{datetime.now()}|>', includeContext=True) # 自定义前缀+显示上下文
def ic_hi(hi):
return hi + "!"
if __name__ == '__main__':
ic(ic_hi("hi"))
PySnooper
前面我们提到了print和icecream都会产生调试代码,当调试结束之后我们还需要删除它们,那么有没有一种非侵入式地调试方式呢,答案是肯定的,那就是pysnooper。
pysnooper通过使用装饰器,可以非侵入式地调试代码,并且它输出的信息很详细,我们可以清楚地看到函数的调用层级,可以清楚地看到变量值的变化过程。并且它是支持调试信息的输出位置配置,默认是在控制台输出,当然,我们也可以在日志中输出调试。
如果你写的 Python 代码不能按如期那样运行,你会绞尽脑汁想为啥出错了。虽然你希望有支持断点的成熟调试器,但或许你现在不想去设置这样的调试器。
你想知道哪些行代码是正常运行,哪些行不正常。据说大多数人会在可疑位置使用 print 输出语句。
其实 PySnooper 的作用有点类似,你不用小心翼翼地用 print 输出,只需在想调试的函数中引入一个装饰器。然后得到函数的详细日志,包括运行了哪些行、何时运行,以及何时更改了局部变量。
追踪整个函数
import pysnooper
@pysnooper.snoop()
def number_to_bits(number):
if number:
bits = []
while number:
number, remainder = divmod(number, 2)
bits.insert(0, remainder)
return bits
else:
return [0]
number_to_bits(6)
追踪函数内部指定范围
def foo():
lst = []
for i in range(10):
lst.append(random.randrange(1, 1000))
with pysnooper.snoop(): # 用于调试, 会打印出每一步的值
lower = min(lst)
upper = max(lst)
mid = (lower + upper) / 2
print(lower, mid, upper)
if __name__ == '__main__':
foo()
添加前缀
@pysnooper.snoop(prefix="funcTwo ")
def two(x, y):
z = x + y
return z
if __name__ == '__main__':
two(1,2)
深度(默认是1)
如果深度是那么只打印当前函数执行的过程,如果深度是2那么当前函数内执行的函数也会被打印,以此类推(递归的话需要调整深度)
@pysnooper.snoop(depth=10)
输入内容到文件
@pysnooper.snoop(output='log.txt')
开启多线程调试(可以打印不同线程的信息)
import pysnooper
import time
import threading
@pysnooper.snoop(thread_info=True)
def do_something():
print("-> 线程启动")
time.sleep(1)
print("-> 线程结束")
if __name__ == '__main__':
thread1 = threading.Thread(target=do_something)
thread2 = threading.Thread(target=do_something)
thread1.start()
thread2.start()
thread1.join()
thread2.join()
assert(断言)
断言的好处就是在有问题的地方,直接结束程序,及时止损,方便追踪问题
凡是用print来辅助查看的地方,都可以用断言(assert)来替代:
def foo(s):
n = int(s)
assert n != 0, 'n is zero!'
return 10 / n
if __name__ == '__main__':
foo('0')
assert的意思是,n不能是0,否则后面的代码就会出错。
pdb(单步调试)
在linux…服务器上写脚本,怎么debug呢? 在windows有IDEA可以进行debug但是在服务器上呢?,不着急Python有pdb可以单步调试代码
python -m pdb a.py
- 输入命令
n
执行下一条语句( 如果本句是函数调用,则执行函数,接着执行当前执行语句的下一条。) - 输入命令
s
执行下一条语句(如果本句是函数调用,则s会执行到函数的第一句) - 输入命令
c
继续执行,直到遇到下一条断点 - 任何时候都可以输入命令
p 变量名
来查看变量 - 输入命令
q
结束调试,退出程序 - 输入
ll
列出全部执行代码和执行情况(如果进入到函数里,那么只显示整个函数), 或者l
列出当前执行语句周围11条代码 - 输入
r
(跳转到函数结束,函数的最后一行) - 输入
u
返回上一层(函数内返回到函数外的当前行) - 输入
b 行数
,或者b 函数名称
添加断点,如果是给函数打断点, 那么会在函数第一条可执行语句处添加断点,还可以跨文件打断点b 文件名:行数
或者b 文件.函数名
,还可以在设置断点的同时设置条件b 行数,条件
- 输入tbreak设置临时断点,用法和b一样, 但是在第一次执行到这个断点之后,就自动删除这个断点
- 输入
b
查询以添加的断点,如果没有那么啥也不打印 - 输入
cl
,或者cl 断点编号
清除断点,断点编号可以使用b查询 - 输入
disable 断点编号
停用断点 ,断点依然存在,只是不启用 - 输入
enable 断点编号
启动断点 - 输入
run
重启debug. - 输入
condition 断点编号 代码条件
给断点设置条件,当参数condition返回True的时候bpnumber断点有效,否则bpnumber断点无效(可以使用b查询断点设置的条件) - 直接输入Enter,会重复执行上一条命令
- 输入PDB不认识的命令,PDB会把他当做Python语句在当前环境下执行;
- 输入
display 变量
每次表达式的值发生改变时才会显示 ,如果直接输入display显示所有设置的监控 - 输入
undisplay 变量
取消display的表达式,如果直接输入undisplay那么就是取消所有 - 输入
w
可以查看当前调用栈(调用栈的信息是从下往上看的) - 输入
a
可查询当前函数的参数 - 输入
j 行号
跳转任意位置执行(注意: 要符合程序运行逻辑,否则会出现错误,假设定义函数是在前10行,而我直接跳转到第15行运行函数,那么肯定无法执行成功的,因为函数没有被定义,解决办法就是回到定义函数位置将函数定义都执行一遍,然后在跳转到运行函数的位置即可,可以搭配unt直接跳转到最后一行进行快速预加载,然后在使用j进行随意跳转测试代码) - 输入
unt 行号
一直执行到某行(不能倒退执行),如果直接输入unt
(跳出循环执行下一行) - 输入
h
查看所有可用的命令。输入h <topic>
某个命令的帮助文档。输入h pdb
查看 pdb 的全部文档
Linux环境可视化调试pudb
pudb是全屏的基于控制台的可视化调试器。
先概要看一下pudb的特性:
- 源码语法高亮,栈、断点、变量可见并且一直动态更新。变量展示还有很多可以定制化的功能。
- 基于键盘,简单高效。为什么说高效呢?因为它支持VI的鼠标移动。还支持PDB的某些命令
- 支持查找源代码,可以使用m代用module browser查看载入的模块
- 断点设置:鼠标移到某行代码,按b,然后可以在断点窗口编辑断点
- PuDB看重异常处理,post-mortem模式使折回到crash的最后一步更简单
在使用前需要安装包pip3 install pudb
` , 为 了代码支持pudb,需要在代码头部插入import pudb; pu.db
`
然后运行代码python test.py
`
import pudb; pu.db
def findinspt(x, xnew):
n = len(x)
for i in range(n):
if x[i] == xnew:
return i
if __name__ == "__main__":
y = [5, 12, 13]
print(findinspt(y, 3))
print(findinspt(y, 8))
print(findinspt(y, 12))
print(findinspt(y, 30))
进入debug页面之后,我们需要设置代码序号,按住Ctrl+p
然后按下→键,之后回车保存即可
不出意外会得到下面的窗口,左半边是源代码,右边一次是变量窗口、程序调用栈、断点
按住Ctrl+x进入命令输入模式(可以输入python代码以及输出变量内容)
在按Ctrl+x就取消命令输入模式,进入到debug模式
在debug模式下的操作(按下Shift+?帮助页面)
- n执行下一条语句
- s执行下一条语句(如果遇到函数则进入到函数里)
- t运行到光标处(上下键移动光标,注意不能后退只能前进)
- c继续执行,直到遇到下一条断点
- r 结束当前函数
- Ctrl+e (切换到编辑nano修改代码,但是不会在本次debug生效)
- b 设置断点(会一直保留,下次debug还存在)
- u 返回到上一个堆栈
- o 切换到结果输出页面(回车切换回来)
- Ctrl+d(切换到下一页)Ctrl+u(切换到上一页)
- g(开头行)G(结束行)
- L(跳转到指定行)
- / 搜索内容,回车确认搜索(使用.切换到下一个搜索结果)
- q 退出debug
在命令模式下的操作
上面提到了Ctrl+x就进入到命令模式了,可以输入python代码,也可以输出当前debug的变量
- Tab 代码补全
- Ctrl+n(上一个历史命令) Ctrl+p(下一个历史命令)
- Ctrl+v 新开一行
右侧,侧边栏
- +(变宽) -(变窄)
右侧,侧边栏,变量操作
- 选择指定的变量,回车展开和关闭(字典,列表等…),或者使用[]展开和关闭
- d(默认显示)t(显示类型)r(显示返回值)i(内存地址)
- n 添加表达式
- delete 删除表达式 (不能删除非自己创建的)
日志logging
Python logging 模块定义了为应用程序和库实现灵活的事件日志记录的函数和类。
程序开发过程中,很多程序都有记录日志的需求,并且日志包含的信息有正常的程序访问日志还可能有错误、警告等信息输出,Python 的 logging 模块提供了标准的日志接口,可以通过它存储各种格式的日志,日志记录提供了一组便利功能,用于简单的日志记录用法。
日志级别等级排序:critical > error > warning > info > debug级别越高打印的日志越少,反之亦然,即
Logging 模块提供了两种日志记录方式:
- 一种方式是使用 Logging 提供的模块级别的函数
- 另一种方式是使用 Logging 日志系统的四大组件记录
简单打印日志:
import logging
# 打印日志级别
def test_logging():
logging.debug('Python debug')
logging.info('Python info')
logging.warning('Python warning')
logging.error('Python Error')
logging.critical('Python critical')
test_logging()
当指定一个日志级别之后,会记录大于或等于这个日志级别的日志信息,小于的将会被丢弃, 默认情况下日志打印只显示大于等于 WARNING 级别的日志。
通过 logging.basicConfig() 可以设置 root 的日志级别,和日志输出格式。
import logging
# 打印日志级别
def test_logging():
logging.basicConfig(level=logging.DEBUG) # 设置
logging.debug('Python debug')
logging.info('Python info')
logging.warning('Python warning')
logging.error('Python Error')
logging.critical('Python critical')
logging.log(2,'test')
if __name__ == "__main__":
test_logging()
将日志信息记录到文件
logging.basicConfig(filename='d:/test.log', level=logging.DEBUG)
在相应的路径下会有 test.log 日志文件,并且日志写入在里面了
显示信息的日期及更改显示消息格式
# 2019-10-16 18:57:45,988 is when this event was logged.
logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
更改显示消息格式
logging.basicConfig(format='%(levelname)s:%(message)s',level=logging.DEBUG)
logging模块是python内置的标准模块,主要用于输出运行日志,可以设置输出日志的等级、日志保存路径、日志文件和回滚等;可以说,logging模块主要由4部分组成:
封装的工具
import logging
import traceback
import datetime
from logging.handlers import RotatingFileHandler
import colorlog
from src.file.FileBasics import FileBasics
class Log(object):
log_colors_config = {
'DEBUG': 'cyan',
'INFO': 'green',
'WARNING': 'yellow',
'ERROR': 'red',
'CRITICAL': 'red',
}
logger = logging.getLogger(__name__)
logger.setLevel(level=logging.INFO)
# 定义一个RotatingFileHandler,最多备份10个日志文件,每个日志文件最大1m
rHandler = RotatingFileHandler(FileBasics.getRootPath()+"LOG/" + str(datetime.date.today()) + '.log', maxBytes=1024 * 1024, backupCount=10)
rHandler.setLevel(logging.INFO)
file_formatter = logging.Formatter('[%(asctime)s-%(filename)s[line:%(lineno)d]-%(levelname)s:%(message)s]')
console_formatter = colorlog.ColoredFormatter(
'%(log_color)s[%(asctime)s-%(filename)s[line:%(lineno)d]-%(levelname)s:%(message)s]',
log_colors=log_colors_config)
rHandler.setFormatter(file_formatter)
console = logging.StreamHandler()
console.setLevel(logging.INFO)
console.setFormatter(console_formatter)
logger.addHandler(rHandler)
logger.addHandler(console)
@staticmethod
def logError(message=None):
if message is None:
Log.logger.error(traceback.format_exc())
else:
Log.logger.error(message)
Log.logger.error(traceback.format_exc())
@staticmethod
def logInfo( message):
Log.logger.info(message)
@staticmethod
def logDebug( message):
Log.logger.debug(message)
文章出处登录后可见!