PySnooper,一个有趣的 Python 库!

PySnooper,一个有趣的 Python 库!

在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值得加入日常工具箱。

© 版权声明

相关文章

暂无评论

none
暂无评论...