Python Debug(调试)的终极指南
作者头像
  • 胡云畅
  • 2020-07-19 08:20:04 1

前言

即使您编写了清晰易读的代码,即便您是经验丰富的开发者,奇怪的bug仍然会在不经意间出现。这时,您需要以某种方式调试这些问题。许多人习惯于使用大量的print语句来查看代码中的运行情况。然而,这种方法并不是最佳选择。实际上,有许多更好的方法可以帮助您定位代码中的错误。本文将探讨这些问题及其解决方法。

日志是必不可少的

如果没有设置日志记录,编写应用程序时最终可能会后悔莫及。缺乏日志的应用程序会让排查问题变得异常困难。幸运的是,在Python中设置基本的日志记录非常简单:

```python import logging

logging.basicConfig( filename='application.log', level=logging.WARNING, format='[%(asctime)s] {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s', datefmt='%H:%M:%S' )

logging.error("发生了一个严重的错误。") logging.warning("您正在使用的功能已弃用。") ```

这段代码创建了一个名为application.log的日志文件,其中包含时间戳、文件路径、行号、日志级别以及消息。虽然这种设置已经足够实用,但拥有配置良好、格式化的日志会使得工作更加轻松。可以通过配置文件(如.ini.yaml)来优化日志配置。

例如,可以使用以下.yaml配置文件来优化日志设置:

yaml version: 1 disable_existing_loggers: true formatters: standard: format: "[%(asctime)s] {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s" datefmt: '%H:%M:%S' handlers: console: class: logging.StreamHandler level: DEBUG formatter: standard stream: ext://sys.stdout file: class: logging.handlers.RotatingFileHandler level: WARNING formatter: standard filename: /tmp/warnings.log maxBytes: 10485760 backupCount: 10 encoding: utf8 root: level: ERROR handlers: [console, file] loggers: mymodule: level: INFO handlers: [file] propagate: no

通过将这些配置保存在.yaml文件中,可以轻松地加载和调整多个日志记录器。使用yaml.safe_load函数从.yaml文件中加载配置,并将其传递给logging.config.dictConfig函数:

```python import yaml from logging import config

with open("config.yaml", 'rt') as f: configdata = yaml.safeload(f.read()) config.dictConfig(config_data) ```

可读的__repr__方法

为了使代码更具可调试性,可以在类中添加__repr__方法。__repr__方法返回一个类实例的字符串表示,通常用于调试目的。最佳实践是返回可用于重新创建实例的文本。例如:

```python class Circle: def init(self, x, y, radius): self.x = x self.y = y self.radius = radius

def __repr__(self):
    return f"Circle({self.x}, {self.y}, {self.radius})"

c = Circle(100, 80, 30) print(repr(c)) # 输出:Circle(100, 80, 30) ```

除了__repr__,在调用print(instance)时,也可以实现__str__方法以提供更多信息。

自定义字典的__missing__方法

如果您需要实现自定义字典类,可以考虑添加__missing__方法。该方法在尝试访问不存在的键时被调用,可以用来记录相关信息,从而避免在代码中到处查找丢失的键。例如:

python class MyDict(dict): def __missing__(self, key): message = f'{key} 不在字典中!' logging.warning(message) return message # 或者抛出异常

这个简单的实现会记录丢失键的消息,但您也可以记录更多有价值的信息,以便更好地了解代码中的问题。

调试崩溃的应用程序

如果您的应用程序在崩溃之前没有给您足够的调试时间,可以使用一些技巧来调试。例如,使用-i参数运行应用程序(python3 -i app.py),这样程序会在启动时进入交互式shell。此时,您可以检查变量和函数。

此外,您还可以使用强大的工具——Python调试器(pdb)。pdb提供了许多功能,可以单独撰写文章来详细说明。例如:

```python

crashing_app.py

SOME_VAR = 42

class SomeError(Exception): pass

def func(): raise SomeError("发生了一些错误...")

func() ```

运行这个脚本并进入交互式调试环境:

bash $ python3 -i crashing_app.py

进入调试模式后,您可以使用pdb命令来检查变量和代码:

```python

import pdb pdb.pm() # 启动后处理调试器 ```

在调试器中,您可以使用各种命令,如p来打印变量值,l来列出代码片段等。

使用堆栈跟踪

如果您的代码在远程服务器上运行,无法获得交互式调试会话,可以使用tracebacksys包来了解代码中的错误位置:

```python import traceback import sys

def func(): try: raise SomeError("发生了一些错误...") except: traceback.print_exc(file=sys.stderr) ```

这段代码会在运行时打印最后一个引发的异常。此外,您还可以使用traceback包来打印堆栈跟踪(traceback.print_stack())或提取原始堆栈帧并进行进一步分析(traceback.format_list(traceback.extract_stack()))。

动态加载模块

在交互式shell中调试或实验某些函数时,您可能需要频繁修改这些函数。为了简化测试和修改的循环,可以使用importlib.reload模块来重新加载模块,而无需重启交互式会话:

```python import func from module func() # 输出:这是结果...

修改 func

from importlib import reload reload(module) func() # 输出:新的结果... ```

这种技术可以提高工作效率,避免重复加载模块带来的不便。

总结

调试是一门艺术。通过合理利用日志记录、自定义方法、调试器和其他工具,您可以有效地定位和解决问题。希望本文提供的技巧能帮助您更好地调试Python代码。

    本文来源:图灵汇
责任编辑: : 胡云畅
声明:本文系图灵汇原创稿件,版权属图灵汇所有,未经授权不得转载,已经协议授权的媒体下载使用时须注明"稿件来源:图灵汇",违者将依法追究责任。
    分享
调试终极指南PythonDebug
    下一篇