Python-调试(各种方式)

文章目录

  • 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)


点赞 -收藏-关注-便于以后复习和收到最新内容
有其他问题在评论区讨论-或者私信我-收到会在第一时间回复
在本博客学习的技术不得以任何方式直接或者间接的从事违反中华人民共和国法律,内容仅供学习、交流与参考
免责声明:本文部分素材来源于网络,版权归原创者所有,如存在文章/图片/音视频等使用不当的情况,请随时私信联系我、以迅速采取适当措施,避免给双方造成不必要的经济损失。
感谢,配合,希望我的努力对你有帮助^_^

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
社会演员多的头像社会演员多普通用户
上一篇 2023年6月25日
下一篇 2023年6月25日

相关推荐