即使您编写了清晰易读的代码,即便您是经验丰富的开发者,奇怪的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
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
来列出代码片段等。
如果您的代码在远程服务器上运行,无法获得交互式调试会话,可以使用traceback
和sys
包来了解代码中的错误位置:
```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() # 输出:这是结果...
from importlib import reload reload(module) func() # 输出:新的结果... ```
这种技术可以提高工作效率,避免重复加载模块带来的不便。
调试是一门艺术。通过合理利用日志记录、自定义方法、调试器和其他工具,您可以有效地定位和解决问题。希望本文提供的技巧能帮助您更好地调试Python代码。