为了账号安全,请及时绑定邮箱和手机立即绑定

用 `ic()` 替换 `print()`,让你的Python调试更轻松

标签:
Python

打印 vs 冰淇淋:Python 中的比较

传统的调试 Python 代码的方法是用 print(),虽然它有效,但也有不足之处,例如输出杂乱无章、需要花费时间标记,以及难以准确确定调试消息的具体来源。

print()的一个类似选项是来自IceCream中的ic()函数——它是一个旨在提升你的调试能力的工具,让你的调试变得更出色。

本指南将教你,你将学到:

  1. 如何为Python项目搭建虚拟环境?
  2. 如何在现有代码中将print()替换为ic()
  3. ic()是如何工作的,以及它为什么比print()更好。
配置环境

在开始编码之前,创建一个虚拟环境来隔离项目依赖项。

使用 Windows:

    python -m venv venv  # 创建一个虚拟环境。
    venv\Scripts\activate  # 激活虚拟环境。
    pip install icecream  # 安装调试或日志工具 icecream。

在 Linux 中:

运行命令来创建虚拟环境:

python3 -m venv venv

激活虚拟环境:

source venv/bin/activate

安装一个名为 icecream 的 Python 库:

pip install icecream


# 示例代码:用 `print()` 和 `ic()` 调试

在你的项目目录下创建一个名为 `math_operations.py` 的文件。编写如下代码:

## `math_operations.py` 数学运算脚本 (shùxué yùnzuǎn jiǎoběn)
def divide_numbers(a, b):  
    print("输入为 a:", a, "b:", b)  # 调试输出  
    result = a / b  
    print("结果为:", result)  # 调试输出  
    return result  

if __name__ == "__main__":  
    num1 = 10  
    num2 = 0  # 故意的错误:除零  
    divide_numbers(num1, num2)

运行上述代码会得到类似这样的结果...

(venv) PS C:\icecream> & C:/icecream/venv/Scripts/python.exe c:/icecream/math_operations.py
输入 - a: 10 b: 0
跟踪回溯(最近的调用):
文件 "c:\icecream\math_operations.py",第 10 行,在 <模块> 中
divide_numbers(num1, num2)
文件 "c:\icecream\math_operations.py",第 3 行,在 divide_numbers 函数中
result = a / b
^
零除错误: 除以零的错误


# 为 `print()` 使用 `ic()`,`ic()` 是更方便的调试工具。

将代码修改为使用 `ic()`,以提高代码的可读性。

## 更新了 `math_operations.py`
from icecream import ic  

def divide_numbers(a, b):  
    ic(a, b)  # 用 ic 调试一下  
    result = a / b  
    ic(result)  # 用 ic 输出  
    return result  

if __name__ == "__main__":  
    num1 = 10  
    num2 = 0  # 这里故意设了个错:用零做除数  
    divide_numbers(num1, num2)

运行上述代码会得到类似这样的结果。

(venv) PS C:\icecream> & C:/icecream/venv/Scripts/python.exe c:/icecream/math_operations.py
ic| a: 10, b: 0
跟踪回溯 (最近的调用):
文件 "c:\icecream\math_operations.py", 第 12 行, 在 <模块> 中
divide_numbers(num1, num2)
文件 "c:\icecream\math_operations.py", 第 5 行, 在 divide_numbers 函数中
result = a / b
^
ZeroDivisionError: 除以零错误


看起来差别不大,但还是有些差异。我们来深入对比一下print和ic这两个命令。

# 1. ic() 自动为变量添加了标签

使用 `print()` 时,你需要明确写出变量名称。
打印:变量 a: a, 变量 b: b

当需要在成百上千行代码中调试,检查更多的变量时,更新这些标签会变得非常繁琐。使用 `ic()`,你只需传递变量,它就会自动为它们打上标签:
ic(a, b)

这些小的时间节省在调试多个变量和函数时会累积,从而使调试过程更高效。

# 2\. **文件和行号详情:**

让我们开启完整上下文。
from icecream import ic  

# 启用完整调试上下文(包括文件名和行号)  
ic.configureOutput(includeContext=True)  

def divide_numbers(a, b):  
    ic(a, b)  
    result = a / b  
    ic(result)  
    return result  

if __name__ == "__main__":  
    num1 = 10  
    num2 = 0  # 故意的错误:尝试除以零  
    divide_numbers(num1, num2)

运行上述代码会得到类似以下的结果:
ic| math_operations.py:7 在 divide_numbers() 函数中 - a: 10, b: 0  
跟踪回溯(最后调用):  
  文件 "c:\icecream\math_operations.py",第 15 行,在 <模块>  
    divide_numbers(num1, num2)  
  文件 "c:\icecream\math_operations.py",第 8 行  
    result = a / b  
             ~~^~~  
零除错误

`ic()` 语句与调试调用发生的位置(文件名 `math_operations.py` 和行号 7)配对。这个功能特别是在以下情况特别有帮助:

* 调试一个由多个文件组成的大代码库。
* 追踪从程序不同部分调用的函数中的问题。

要使用 `print()` 重现这一点,我们需要手动添加一些上下文信息,
打印("文件名:math_operations.py,代码行:7,变量值 a:", a, "变量值 b:", b)

# 3. 调试链条和中间的成果

让我们先调整一下这个函数,让它更复杂一点,同时加入包含完整上下文的调试信息。
from icecream import ic  

# 开启完整的调试环境  
ic.configureOutput(includeContext=True)  

def divide_numbers(a, b):  
    c = a * 2  
    ic(a, b, c)  # 调试中间结果  
    result = c / b  
    ic(result)  
    return result  

if __name__ == "__main__":  
    num1 = 10  
    num2 = 0  # 特意设置的错误  
    divide_numbers(num1, num2)

结果就是这样。

ic| math_operations.py:8: 在函数 divide_numbers() 中 - a: 10, b: 0, c: 20


`ic()`的输出显示失败时`a`、`b`和`c`的状态,以及文件名和行号。

**为什么更好:** 使用 `print()` 时,你就得自己加这些内容:
print("文件:math_operations.py,第8行,中间结果 - a:", a, " b:", b, " c:", c)

# 4. 代码库中的一致性

调试大型项目时,使用 `print()` 可能会因为缺少上下文信息而变得很有挑战性。而使用 `ic()`,调试就既简单又方便,同时也更加一致和可追踪。

通过在开发中启用调试信息,而在生产中禁用它,你可以确保调试输出在开发过程中有效,且不会污染生产日志。这样,将所有 `print()` 语句替换为 `ic()` 可以让代码更加易于维护且保持一致性。

# 5\. 输出格式和易读性

这里是一个使用 `ic()`(一个特定的调试工具)并开启完整上下文来调试一系列相关函数中问题的例子。
from icecream import ic  

# 开启全面的调试上下文  
ic.configureOutput(includeContext=True)  

def add_numbers(x, y):  
    return x + y  

def multiply_numbers(x, y):  
    return x * y  

def compute(a, b):  
    ic(a, b)  # 调试输入  
    total = add_numbers(a, b)  
    ic(total)  # 调试中间值  
    product = multiply_numbers(total, a)  
    ic(product)  # 调试最终值  
    return product  

if __name__ == "__main__":  
    compute(10, 0)  # 计算10和0的结果

结果:

在虚拟环境中运行Python脚本的结果如下:

(venv) PS C:\icecream> & C:/icecream/venv/Scripts/python.exe c:/icecream/math_operations.py
ic| math_operations.py:13 在计算中 - a: 10, b: 0
ic| math_operations.py:15 在计算中 - 总数: 10
ic| math_operations.py:17 在计算中 - 乘积: 100


**主要优势:**

* 每个调试信息都会标注函数名和行号。
* 输出会清晰地显示出值是如何随函数调用而变化的。

你得自己手动格式化和标记每一条调试信息,使用 `print()` 时。
print("文件名:math_operations.py,函数名:compute,行:13,变量 a 和 b:", a, "b:", b)

# 6. 简便的快速调试

当你测试新逻辑时,你可以专注于你的代码,而不用担心其他琐事,而无需担心冗长的格式问题。这里有一个简单的例子:
from icecream import ic  

# 启用完整调试上下文  

# 这个设置用于开启完整的调试信息输出  
ic.configureOutput(includeContext=True)  

def quick_test(x):  
    y = x + 1  
    z = y * 2  
    # 这行代码输出x, y, z的值  
    ic(x, y, z)  
    return z  

# 这是一个简单的测试函数,用于演示如何使用ic库进行调试输出  
if __name__ == "__main__":  
    quick_test(5)

结果是

(venv) PS C:\icecream> & C:/icecream/venv/Scripts/python.exe c:/icecream/math_operations.py
提示| math_operations.py:9 in 快速测试() - x: 5, y: 6, z: 12


**为什么更好呢:**

* 所有变量的状态会在一行中显示,并附带上下文信息。
* 你不用特意标记变量或追踪消息的来源。

使用`ic()`配置的调试信息的强大之处

通过将 `ic()` 配置为将其输出重定向到调试日志,你就能解锁详细、持久且包含丰富上下文的调试信息的全部潜力。以下是配置 `ic()` 进行日志记录,使你的调试过程变得更强大的方法。

将 `math_operations.py` 文件中的现有代码替换为下面的代码:

```python
    import time  
    from icecream import ic  

    # 自定义输出函数,确保每个调试条目独占一行  
    def custom_log_function(output):  
        debug_log_file.write(output + '\n')  # 每个条目后添加一个换行符  

    # 打开日志文件进行写入  
    debug_log_file = open("debug.log", "w")  

    # 配置IceCream以使用自定义输出函数进行日志记录  
    ic.configureOutput(prefix='DEBUG -> ', outputFunction=custom_log_function)  

    def calculate_sum(a, b):  
        ic(a, b)  
        result = a + b  
        ic(result)  
        return result  

    def calculate_division(a, b):  
        try:  
            ic(a, b)  
            result = a / b  
            ic(result)  
            return result  
        except ZeroDivisionError as e:  
            ic("捕获到错误:", e)  
            return None  

    def pause_execution():  
        ic("暂停执行,2秒...")  
        time.sleep(2)  
        ic("恢复执行...")  

    if __name__ == "__main__":  
        ic("开始调试,演示...")  

        # 显示变量值并进行计算  
        x, y = 10, 5  
        ic(x, y)  
        sum_result = calculate_sum(x, y)  

        # 演示处理错误  
        zero_division_result = calculate_division(x, 0)  

        # 暂停执行  
        pause_execution()  

        # 演示成功除法  
        valid_division_result = calculate_division(x, y)  

        ic("调试演示完成.")  

        # 清理:关闭,调试日志文件  
        debug_log_file.close()

现在,debug.log 文件里的内容会每条记录独占一行,格式也会变得整齐。

    DEBUG -> '调试演示开始...'  
    DEBUG -> x: 10, y: 5  
    DEBUG -> a: 10, b: 5  
    DEBUG -> result: 15  
    DEBUG -> a: 10, b: 0  
    DEBUG -> '捕获到一个错误:', e: 零除错误('除数为零的错误')  
    DEBUG -> '暂停执行2秒钟...'  
    DEBUG -> '恢复执行...'  
    DEBUG -> a: 10, b: 5  
    DEBUG -> result: 2.0  
    DEBUG -> '调试演示完成.'

扩展的ic()配置列表:

    from icecream import ic
    import sys

    # ic() 的常见配置设置

    # 1. 启用完整调试上下文
    # 向所有调试输出添加文件名、行号和函数名,以提供更详细的调试信息。
    ic.configureOutput(includeContext=True)

    # 2. 更改输出前缀(可选)
    # 当你希望自定义调试输出的前缀,使之更易于识别时很有用。
    ic.configureOutput(prefix='DEBUG -> ')

    # 3. 更改输出目标(例如,写入文件)
    # 默认情况下,IceCream 输出到 stderr。你可以重定向到文件。
    debug_log_file = open("debug.log", "w")  # 日志将保存在这里
    ic.configureOutput(outputFunction=debug_log_file.write)

    # 4. 全局禁用 ic()(用于生产环境)
    # 在生产环境中调用 `ic.disable()` 以抑制所有调试输出。
    # ic.disable()

    # 5. 重新启用 ic()(用于开发环境)
    # 调用 `ic.enable()` 以重新启用被禁用的调试输出,以便在开发过程中进行调试。
    # ic.enable()

    # 6. 向调试输出添加时间戳
    # 记录每个调试语句执行的时间。
    from datetime import datetime
    def custom_log_with_time(output):
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        return f"[{timestamp}] {output}\n"
    ic.configureOutput(outputFunction=lambda output: debug_log_file.write(custom_log_with_time(output)))

    # 7. 过滤特定调试消息
    # 只记录满足特定条件的消息(例如,包含 "ERROR")。
    def filter_errors(output):
        if "ERROR" in output:
            debug_log_file.write(output + '\n')  # 只将错误消息写入日志文件。
    ic.configureOutput(outputFunction=filter_errors)

    # 8. 在输出中强调变量
    # 为调试变量添加强调(例如,大写或格式化)。
    def emphasize_variables(output):
        return output.upper()  # 将调试输出转换为大写以强调。
    ic.configureOutput(outputFunction=lambda output: debug_log_file.write(emphasize_variables(output) + '\n'))

    # 9. 将 IceCream 与实时监控结合使用
    # 使用类似 `tail -f debug.log` 的命令在 Unix 系统中实时监控日志文件。
    # 示例:对于持续运行的应用程序,可以使用实时日志记录来实时监控应用程序的状态。

    # 10. 使用 IceCream 添加上下文元数据
    # 向日志中添加额外的元数据,如环境信息(例如,开发环境或生产环境)或应用程序状态,以提供更多的上下文信息。
    def contextual_logging(output):
        metadata = "[ENV: DEVELOPMENT]"
        return f"{metadata} {output}\n"
    ic.configureOutput(outputFunction=lambda output: debug_log_file.write(contextual_logging(output)))

感谢您阅读这篇文章。希望您觉得它既有帮助又有信息量。如果您有任何问题,或者想要提出新的 Python 代码示例或未来教程的主题,请随时联系。您的反馈和建议总是很受欢迎!

祝你编程愉快!
C. C. Python编程

简明英语

感谢你加入在白话英语社区,在你离开之前,想对你说:

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消