打印 vs 冰淇淋:Python 中的比较
传统的调试 Python 代码的方法是用 print()
,虽然它有效,但也有不足之处,例如输出杂乱无章、需要花费时间标记,以及难以准确确定调试消息的具体来源。
print()
的一个类似选项是来自IceCream库中的ic()
函数——它是一个旨在提升你的调试能力的工具,让你的调试变得更出色。
本指南将教你,你将学到:
- 如何为Python项目搭建虚拟环境?
- 如何在现有代码中将
print()
替换为ic()
。 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编程
感谢你加入在白话英语社区,在你离开之前,想对你说:
- 记得为作者鼓掌👏👏并关注她
- 关注我们: X | LinkedIn | YouTube | Discord | 邮件通讯 | 播客频道
- 在Differ上免费创建一个AI驱动的博客。
- 更多内容请查看 PlainEnglish.io
共同学习,写下你的评论
评论加载中...
作者其他优质文章