
在Python开发过程中,调试是定位和解决问题的关键环节。传统的调试方式要么使用print语句逐行输出,繁琐且容易遗漏;要么使用专业调试器,学习成本较高。PySnooper是一个轻量级的调试工具,只需添加一个装饰器,就能自动记录函数执行过程中的每一行代码、变量变化和返回值。它就像给代码装上了一个”监控摄像头”,让程序的执行过程一目了然,特别适合快速定位问题而不想大费周章的场景。
安装
1、安装方法
使用pip可以快速安装PySnooper:
pip install pysnooper
2、验证安装
安装完成后,通过以下代码验证是否安装成功:
import pysnooper
@pysnooper.snoop()
def add(a, b):
result = a + b
return result
add(1, 2)
输出结果:
Source path:... /test2.py
Starting var:.. a = 1
Starting var:.. b = 2
14:22:16.475510 call 4 def add(a, b):
14:22:16.475694 line 5 result = a + b
New var:....... result = 3
14:22:16.475710 line 6 return result
14:22:16.475723 return 6 return result
Return value:.. 3
Elapsed time: 00:00:00.000270
如果控制台输出了函数执行的详细过程,包括变量赋值和返回值,说明安装成功。
核心特性
- 零侵入:只需添加装饰器,无需修改原有代码逻辑
- 信息全面:自动记录代码行号、变量值、执行时间
- 灵活输出:支持输出到控制台、文件或自定义流
- 深度追踪:可选择性追踪嵌套函数调用
- 变量过滤:支持监控特定变量或表达式
- 性能友善:调试完成后删除装饰器即可,无残留影响
基本功能
1、基础调试追踪
最简单的使用方式是直接给函数添加@pysnooper.snoop()装饰器。PySnooper会记录函数内每一行代码的执行情况,包括当前执行的代码、局部变量的创建和修改过程。
import pysnooper
@pysnooper.snoop()
def calculate_factorial(n):
"""计算阶乘"""
if n <= 1:
return 1
result = 1
for i in range(2, n + 1):
result *= i
return result
# 调用函数,观察执行过程
factorial_5 = calculate_factorial(5)
print(f"5的阶乘是: {factorial_5}")
执行后,控制台会显示类似以下的输出:
Source path:... test.py
Starting var:.. n = 5
14:23:58.445457 call 5 def calculate_factorial(n):
14:23:58.445625 line 7 if n <= 1:
14:23:58.445640 line 9 result = 1
New var:....... result = 1
14:23:58.445650 line 10 for i in range(2, n + 1):
New var:....... i = 2
14:23:58.445662 line 11 result *= i
Modified var:.. result = 2
14:23:58.445674 line 10 for i in range(2, n + 1):
Modified var:.. i = 3
14:23:58.445684 line 11 result *= i
Modified var:.. result = 6
14:23:58.445693 line 10 for i in range(2, n + 1):
Modified var:.. i = 4
14:23:58.445702 line 11 result *= i
Modified var:.. result = 24
14:23:58.445710 line 10 for i in range(2, n + 1):
Modified var:.. i = 5
14:23:58.445720 line 11 result *= i
Modified var:.. result = 120
14:23:58.445728 line 10 for i in range(2, n + 1):
14:23:58.445737 line 12 return result
14:23:58.445743 return 12 return result
Return value:.. 120
Elapsed time: 00:00:00.000342
2、输出到文件
在调试复杂问题时,控制台输出可能过于冗长不便查看。PySnooper支持将调试信息输出到文件,方便事后分析和归档。只需在装饰器中指定输出路径,所有追踪信息都会写入指定文件,便于反复查阅和搜索关键信息。
import pysnooper
@pysnooper.snoop(output='debug_log.txt')
def process_data(data_list):
"""处理数据列表"""
results = []
for item in data_list:
if item > 0:
processed = item * 2
else:
processed = 0
results.append(processed)
return results
data = [1, -2, 3, -4, 5]
result = process_data(data)
print("处理完成,日志已保存到 debug_log.txt")
输出结果:
处理完成,日志已保存到 debug_log.txt
Source path:... /test2.py
Starting var:.. data_list = [1, -2, 3, -4, 5]
14:24:25.116477 call 4 def process_data(data_list):
14:24:25.117319 line 6 results = []
New var:....... results = []
14:24:25.117430 line 7 for item in data_list:
New var:....... item = 1
14:24:25.117607 line 8 if item > 0:
14:24:25.117784 line 9 processed = item * 2
New var:....... processed = 2
14:24:25.117869 line 12 results.append(processed)
Modified var:.. results = [2]
14:24:25.118022 line 7 for item in data_list:
Modified var:.. item = -2
14:24:25.118169 line 8 if item > 0:
14:24:25.118302 line 11 processed = 0
Modified var:.. processed = 0
14:24:25.118360 line 12 results.append(processed)
Modified var:.. results = [2, 0]
14:24:25.118463 line 7 for item in data_list:
Modified var:.. item = 3
14:24:25.118576 line 8 if item > 0:
14:24:25.118726 line 9 processed = item * 2
Modified var:.. processed = 6
14:24:25.118818 line 12 results.append(processed)
Modified var:.. results = [2, 0, 6]
14:24:25.119002 line 7 for item in data_list:
Modified var:.. item = -4
14:24:25.119164 line 8 if item > 0:
14:24:25.119319 line 11 processed = 0
Modified var:.. processed = 0
14:24:25.119394 line 12 results.append(processed)
Modified var:.. results = [2, 0, 6, 0]
14:24:25.119537 line 7 for item in data_list:
Modified var:.. item = 5
14:24:25.119680 line 8 if item > 0:
14:24:25.119837 line 9 processed = item * 2
Modified var:.. processed = 10
14:24:25.119912 line 12 results.append(processed)
Modified var:.. results = [2, 0, 6, 0, 10]
14:24:25.120054 line 7 for item in data_list:
14:24:25.120210 line 13 return results
14:24:25.120295 return 13 return results
Return value:.. [2, 0, 6, 0, 10]
Elapsed time: 00:00:00.004018
3、监控特定变量
有时函数中变量众多,我们只关心其中几个关键变量的变化。使用watch参数可以额外监控指定的变量或表达式,即使是全局变量或复杂表达式也能追踪。
import pysnooper
total_count = 0
@pysnooper.snoop(watch=('total_count', 'len(items)'))
def batch_process(items):
"""批量处理并统计"""
global total_count
processed = []
for item in items:
total_count += 1
processed.append(item.upper())
return processed
result = batch_process(['apple', 'banana', 'cherry'])
print(f"处理结果: {result}")
print(f"累计处理: {total_count} 项")
输出结果:
Source path:... /test2.py
Starting var:.. items = ['apple', 'banana', 'cherry']
Starting var:.. total_count = 0
Starting var:.. len(items) = 3
14:25:26.911656 call 6 def batch_process(items):
14:25:26.911872 line 9 processed = []
New var:....... processed = []
14:25:26.911892 line 10 for item in items:
New var:....... item = 'apple'
14:25:26.911909 line 11 total_count += 1
Modified var:.. total_count = 1
14:25:26.911924 line 12 processed.append(item.upper())
Modified var:.. processed = ['APPLE']
14:25:26.911937 line 10 for item in items:
Modified var:.. item = 'banana'
14:25:26.911949 line 11 total_count += 1
Modified var:.. total_count = 2
14:25:26.911960 line 12 processed.append(item.upper())
Modified var:.. processed = ['APPLE', 'BANANA']
14:25:26.911972 line 10 for item in items:
Modified var:.. item = 'cherry'
14:25:26.911982 line 11 total_count += 1
Modified var:.. total_count = 3
14:25:26.911993 line 12 processed.append(item.upper())
Modified var:.. processed = ['APPLE', 'BANANA', 'CHERRY']
14:25:26.912004 line 10 for item in items:
14:25:26.912015 line 13 return processed
14:25:26.912024 return 13 return processed
Return value:.. ['APPLE', 'BANANA', 'CHERRY']
Elapsed time: 00:00:00.000426
处理结果: ['APPLE', 'BANANA', 'CHERRY']
累计处理: 3 项
高级功能
1、追踪嵌套函数调用
实际项目中函数往往相互调用形成调用链,默认情况下PySnooper只追踪被装饰的函数本身,但通过设置depth参数可以追踪嵌套调用的子函数。
import pysnooper
def helper_add(a, b):
"""辅助加法函数"""
return a + b
def helper_multiply(a, b):
"""辅助乘法函数"""
return a * b
@pysnooper.snoop(depth=2)
def complex_calculation(x, y, z):
"""复杂计算:(x + y) * z"""
sum_result = helper_add(x, y)
final_result = helper_multiply(sum_result, z)
return final_result
result = complex_calculation(2, 3, 4)
print(f"计算结果: {result}")
输出结果:
计算结果: 20
Source path:... /test2.py
Starting var:.. x = 2
Starting var:.. y = 3
Starting var:.. z = 4
14:25:58.076791 call 12 def complex_calculation(x, y, z):
14:25:58.076973 line 14 sum_result = helper_add(x, y)
Starting var:.. a = 2
Starting var:.. b = 3
14:25:58.076994 call 3 def helper_add(a, b):
14:25:58.077012 line 5 return a + b
14:25:58.077022 return 5 return a + b
Return value:.. 5
New var:....... sum_result = 5
14:25:58.077035 line 15 final_result = helper_multiply(sum_result, z)
Starting var:.. a = 5
Starting var:.. b = 4
14:25:58.077047 call 7 def helper_multiply(a, b):
14:25:58.077058 line 9 return a * b
14:25:58.077066 return 9 return a * b
Return value:.. 20
New var:....... final_result = 20
14:25:58.077076 line 16 return final_result
14:25:58.077087 return 16 return final_result
Return value:.. 20
Elapsed time: 00:00:00.000351
2、自定义输出前缀与格式
当同时调试多个函数或模块时,输出信息可能混杂难以区分。PySnooper允许为每个追踪设置独立的前缀标识,还可以控制是否显示相对时间、线程信息等。
import pysnooper
@pysnooper.snoop(prefix='[用户模块] ', relative_time=True)
def user_login(username, password):
"""用户登录逻辑"""
is_valid = len(username) > 3and len(password) >= 6
if is_valid:
token = f"token_{username}_123"
return {'success': True, 'token': token}
return {'success': False, 'error': '验证失败'}
@pysnooper.snoop(prefix='[订单模块] ', relative_time=True)
def create_order(user_id, items):
"""创建订单"""
order_id = f"ORD_{user_id}_{len(items)}"
total = sum(item['price'] for item in items)
return {'order_id': order_id, 'total': total}
# 分别调试
login_result = user_login('admin', 'password123')
order_result = create_order('U001', [{'name': '商品A', 'price': 100}])
输出结果:
[用户模块] Source path:... /test2.py
[用户模块] Starting var:.. username = 'admin'
[用户模块] Starting var:.. password = 'password123'
[用户模块] 00:00:00.000001 call 4 def user_login(username, password):
[用户模块] 00:00:00.000153 line 6 is_valid = len(username) > 3and len(password) >= 6
[用户模块] New var:....... is_valid = True
[用户模块] 00:00:00.000171 line 7 if is_valid:
[用户模块] 00:00:00.000185 line 8 token = f"token_{username}_123"
[用户模块] New var:....... token = 'token_admin_123'
[用户模块] 00:00:00.000194 line 9 return {'success': True, 'token': token}
[用户模块] 00:00:00.000205 return 9 return {'success': True, 'token': token}
[用户模块] Return value:.. {'success': True, 'token': 'token_admin_123'}
[用户模块] Elapsed time: 00:00:00.000264
[订单模块] Source path:... /Users/hulongguang/PycharmProjects/PythonProject1/test2.py
[订单模块] Starting var:.. user_id = 'U001'
[订单模块] Starting var:.. items = [{'name': '商品A', 'price': 100}]
[订单模块] 00:00:00.000000 call 13 def create_order(user_id, items):
[订单模块] 00:00:00.000022 line 15 order_id = f"ORD_{user_id}_{len(items)}"
[订单模块] New var:....... order_id = 'ORD_U001_1'
[订单模块] 00:00:00.000030 line 16 total = sum(item['price'] for item in items)
[订单模块] New var:....... total = 100
[订单模块] 00:00:00.000044 line 17 return {'order_id': order_id, 'total': total}
[订单模块] 00:00:00.000055 return 17 return {'order_id': order_id, 'total': total}
[订单模块] Return value:.. {'order_id': 'ORD_U001_1', 'total': 100}
[订单模块] Elapsed time: 00:00:00.000079
3、使用上下文管理器局部调试
有时只需要调试函数中的某一段代码而非整个函数。PySnooper支持以上下文管理器的方式使用,可以准确控制追踪范围,只监控关键代码块,避免输出过多无关信息。
import pysnooper
def large_function(data):
"""一个较长的函数"""
# 第一阶段:数据预处理(不需要调试)
cleaned = [x.strip() for x in data if x]
# 第二阶段:核心逻辑(需要调试)
with pysnooper.snoop():
result = []
for item in cleaned:
if item.startswith('important'):
processed = item.upper()
result.append(processed)
# 第三阶段:后处理(不需要调试)
final = sorted(result)
return final
test_data = ['important_task1', 'normal', 'important_task2', '']
output = large_function(test_data)
输出结果:
Source path:... /test2.py
New var:....... data = ['important_task1', 'normal', 'important_task2', '']
New var:....... cleaned = ['important_task1', 'normal', 'important_task2']
14:27:10.284518 line 11 result = []
New var:....... result = []
14:27:10.284748 line 12 for item in cleaned:
New var:....... item = 'important_task1'
14:27:10.284773 line 13 if item.startswith('important'):
14:27:10.284790 line 14 processed = item.upper()
New var:....... processed = 'IMPORTANT_TASK1'
14:27:10.284802 line 15 result.append(processed)
Modified var:.. result = ['IMPORTANT_TASK1']
14:27:10.284815 line 12 for item in cleaned:
Modified var:.. item = 'normal'
14:27:10.284828 line 13 if item.startswith('important'):
14:27:10.284841 line 12 for item in cleaned:
Modified var:.. item = 'important_task2'
14:27:10.284850 line 13 if item.startswith('important'):
14:27:10.284861 line 14 processed = item.upper()
Modified var:.. processed = 'IMPORTANT_TASK2'
14:27:10.284870 line 15 result.append(processed)
Modified var:.. result = ['IMPORTANT_TASK1', 'IMPORTANT_TASK2']
14:27:10.284881 line 12 for item in cleaned:
14:27:10.284892 line 10 with pysnooper.snoop():
Elapsed time: 00:00:00.000430
实际应用场景
1、排查递归函数问题
递归函数的执行过程往往难以追踪,PySnooper能够清晰展示每一层递归的参数和返回值:
import pysnooper
@pysnooper.snoop()
def fibonacci(n):
"""斐波那契数列"""
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
# 追踪递归过程
fib_5 = fibonacci(5)
print(f"斐波那契第5项: {fib_5}")
输出结果:
斐波那契第5项: 5
Source path:... /test2.py
Starting var:.. n = 5
14:27:39.324785 call 4 def fibonacci(n):
14:27:39.324967 line 6 if n <= 1:
14:27:39.324983 line 8 return fibonacci(n - 1) + fibonacci(n - 2)
Starting var:.. n = 4
14:27:39.325001 call 4 def fibonacci(n):
14:27:39.325013 line 6 if n <= 1:
14:27:39.325020 line 8 return fibonacci(n - 1) + fibonacci(n - 2)
Starting var:.. n = 3
14:27:39.325031 call 4 def fibonacci(n):
14:27:39.325040 line 6 if n <= 1:
14:27:39.325046 line 8 return fibonacci(n - 1) + fibonacci(n - 2)
Starting var:.. n = 2
14:27:39.325054 call 4 def fibonacci(n):
14:27:39.325063 line 6 if n <= 1:
14:27:39.325069 line 8 return fibonacci(n - 1) + fibonacci(n - 2)
Starting var:.. n = 1
14:27:39.325078 call 4 def fibonacci(n):
14:27:39.325088 line 6 if n <= 1:
14:27:39.325093 line 7 return n
14:27:39.325099 return 7 return n
Return value:.. 1
Elapsed time: 00:00:00.000037
Starting var:.. n = 0
14:27:39.325126 call 4 def fibonacci(n):
14:27:39.325135 line 6 if n <= 1:
14:27:39.325141 line 7 return n
14:27:39.325146 return 7 return n
Return value:.. 0
Elapsed time: 00:00:00.000032
14:27:39.325163 return 8 return fibonacci(n - 1) + fibonacci(n - 2)
Return value:.. 1
Elapsed time: 00:00:00.000123
Starting var:.. n = 1
14:27:39.325184 call 4 def fibonacci(n):
14:27:39.325193 line 6 if n <= 1:
14:27:39.325198 line 7 return n
14:27:39.325204 return 7 return n
Return value:.. 1
Elapsed time: 00:00:00.000030
14:27:39.325217 return 8 return fibonacci(n - 1) + fibonacci(n - 2)
Return value:.. 2
Elapsed time: 00:00:00.000196
Starting var:.. n = 2
14:27:39.325233 call 4 def fibonacci(n):
14:27:39.325242 line 6 if n <= 1:
14:27:39.325247 line 8 return fibonacci(n - 1) + fibonacci(n - 2)
Starting var:.. n = 1
14:27:39.325255 call 4 def fibonacci(n):
14:27:39.325262 line 6 if n <= 1:
14:27:39.325268 line 7 return n
14:27:39.325273 return 7 return n
Return value:.. 1
Elapsed time: 00:00:00.000028
Starting var:.. n = 0
14:27:39.325288 call 4 def fibonacci(n):
14:27:39.325296 line 6 if n <= 1:
14:27:39.325301 line 7 return n
14:27:39.325306 return 7 return n
Return value:.. 0
Elapsed time: 00:00:00.000029
14:27:39.325321 return 8 return fibonacci(n - 1) + fibonacci(n - 2)
Return value:.. 1
Elapsed time: 00:00:00.000099
14:27:39.325335 return 8 return fibonacci(n - 1) + fibonacci(n - 2)
Return value:.. 3
Elapsed time: 00:00:00.000346
Starting var:.. n = 3
14:27:39.325352 call 4 def fibonacci(n):
14:27:39.325361 line 6 if n <= 1:
14:27:39.325367 line 8 return fibonacci(n - 1) + fibonacci(n - 2)
Starting var:.. n = 2
14:27:39.325375 call 4 def fibonacci(n):
14:27:39.325383 line 6 if n <= 1:
14:27:39.325388 line 8 return fibonacci(n - 1) + fibonacci(n - 2)
Starting var:.. n = 1
14:27:39.325396 call 4 def fibonacci(n):
14:27:39.325403 line 6 if n <= 1:
14:27:39.325408 line 7 return n
14:27:39.325413 return 7 return n
Return value:.. 1
Elapsed time: 00:00:00.000028
Starting var:.. n = 0
14:27:39.325429 call 4 def fibonacci(n):
14:27:39.325437 line 6 if n <= 1:
14:27:39.325442 line 7 return n
14:27:39.325447 return 7 return n
Return value:.. 0
Elapsed time: 00:00:00.000028
14:27:39.325460 return 8 return fibonacci(n - 1) + fibonacci(n - 2)
Return value:.. 1
Elapsed time: 00:00:00.000095
Starting var:.. n = 1
14:27:39.325475 call 4 def fibonacci(n):
14:27:39.325483 line 6 if n <= 1:
14:27:39.325488 line 7 return n
14:27:39.325493 return 7 return n
Return value:.. 1
Elapsed time: 00:00:00.000028
14:27:39.325506 return 8 return fibonacci(n - 1) + fibonacci(n - 2)
Return value:.. 2
Elapsed time: 00:00:00.000164
14:27:39.325519 return 8 return fibonacci(n - 1) + fibonacci(n - 2)
Return value:.. 5
Elapsed time: 00:00:00.000783
2、调试异步代码
PySnooper同样支持异步函数的调试,协助理解协程的执行顺序:
import pysnooper
import asyncio
@pysnooper.snoop()
async def fetch_data(url):
"""模拟异步数据获取"""
await asyncio.sleep(0.1)
return f"Data from {url}"
async def main():
result = await fetch_data("https://api.example.com")
print(result)
asyncio.run(main())
3、配合日志系统使用
在生产环境附近调试时,可以将PySnooper输出集成到现有日志系统:
import pysnooper
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
@pysnooper.snoop(output=logger.debug)
def critical_function(value):
processed = value * 2
return processed
总结
PySnooper是一个简洁而强劲的Python调试工具,以最小的代码侵入实现了全面的执行追踪。本文介绍了从基础的装饰器用法、文件输出、变量监控,到高级的嵌套追踪、自定义格式和局部调试等功能。相比传统的print调试和复杂的调试器,PySnooper在易用性和功能性之间取得了良好平衡。对于追求开发效率的Python程序员,PySnooper值得加入日常工具箱。
