程序编写完成或在编写过程中,需要对程序进行测试,根据测试发现的错误,进一步诊断,找出发生错误的原因和具体代码位置进行修改,这个过程称为程序调试。在一些情况下,可能需要查看或跟踪程序的运行状态,这种情况也属于程序调试。
调试最简单的方式就是打印输出,而print函数就可以输出各种类型变量,配合着格式化输出,我们可以打印出程序运行过程中各个变量的状态值。
if __name__ == '__main__':
a=1
print(f'值:{a}') # 值:1
使用这种方式的好处是我们不需要引入其它包,我们只需要使用简单的print就可以调试我们的程序,当然,它的缺点也很明显,有时候为了调试一些变量,我们不得不写很多print语句,而且有时候为了更优雅地显示数据,我们不得不写很多代码。
和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"))
前面我们提到了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()
断言的好处就是在有问题的地方,直接结束程序,及时止损,方便追踪问题
凡是用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,否则后面的代码就会出错。
在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 行数,条件
b
查询以添加的断点,如果没有那么啥也不打印cl
,或者 cl 断点编号
清除断点,断点编号可以使用b查询disable 断点编号
停用断点 ,断点依然存在,只是不启用enable 断点编号
启动断点run
重启debug.condition 断点编号 代码条件
给断点设置条件,当参数condition返回True的时候bpnumber断点有效,否则bpnumber断点无效(可以使用b查询断点设置的条件)display 变量
每次表达式的值发生改变时才会显示 ,如果直接输入display显示所有设置的监控undisplay 变量
取消display的表达式,如果直接输入undisplay那么就是取消所有w
可以查看当前调用栈(调用栈的信息是从下往上看的)a
可查询当前函数的参数j 行号
跳转任意位置执行(注意: 要符合程序运行逻辑,否则会出现错误,假设定义函数是在前10行,而我直接跳转到第15行运行函数,那么肯定无法执行成功的,因为函数没有被定义,解决办法就是回到定义函数位置将函数定义都执行一遍,然后在跳转到运行函数的位置即可,可以搭配unt直接跳转到最后一行进行快速预加载,然后在使用j进行随意跳转测试代码)unt 行号
一直执行到某行(不能倒退执行),如果直接输入unt
(跳出循环执行下一行)h
查看所有可用的命令。输入h
某个命令的帮助文档。输入h pdb
查看 pdb 的全部文档pudb是全屏的基于控制台的可视化调试器。
先概要看一下pudb的特性:
在使用前需要安装包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+?帮助页面)
在命令模式下的操作
上面提到了Ctrl+x就进入到命令模式了,可以输入python代码,也可以输出当前debug的变量
右侧,侧边栏
右侧,侧边栏,变量操作
Python logging 模块定义了为应用程序和库实现灵活的事件日志记录的函数和类。
程序开发过程中,很多程序都有记录日志的需求,并且日志包含的信息有正常的程序访问日志还可能有错误、警告等信息输出,Python 的 logging 模块提供了标准的日志接口,可以通过它存储各种格式的日志,日志记录提供了一组便利功能,用于简单的日志记录用法。
日志级别等级排序:critical > error > warning > info > debug级别越高打印的日志越少,反之亦然,即
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)