
本文旨在解决Sphinx doctest 在处理包含Matplotlib绘图示例的文档字符串时,因 plt.show() 导致测试中断的问题。核心策略是通过重构绘图函数,使其接受可选的 Axes 对象并移除 plt.show() 的直接调用,从而将绘图逻辑与图形显示解耦。这种方法不仅提升了函数的复用性,也确保了 doctest 能够顺畅运行,实现自动化测试与文档生成。
在Python项目中,Sphinx是一个广泛使用的文档生成工具,其内置的 doctest 扩展能够从文档字符串中的示例代码直接运行测试,确保代码示例的准确性。然而,当这些文档字符串包含Matplotlib绘图代码,并且在函数内部调用了 plt.show() 时,doctest 可能会遇到一个常见的阻塞问题。
plt.show() 函数的作用是显示当前活动的Matplotlib图形窗口。在交互式环境中,这通常是期望的行为。但在自动化测试(如 doctest)或非交互式脚本中,plt.show() 会暂停程序执行,直到用户手动关闭图形窗口。这对于自动化流程来说是不可接受的,因为它需要人工干预,严重阻碍了测试的连续性。
考虑以下一个典型的Matplotlib绘图函数及其文档字符串:
import matplotlib.pyplot as plt
def plot_numbers_problematic(x):
"""
显示一组数字的折线图。
参数
----------
x : list
要绘制的数字列表。
示例
-------
>>> import your_module # 假设此函数在your_module中
>>> x_data = [1, 2, 5, 6, 8.1, 7, 10.5, 12]
>>> your_module.plot_numbers_problematic(x_data)
"""
_, ax = plt.subplots()
ax.plot(x, marker="o", mfc="red", mec="red")
ax.set_xlabel("X轴标签")
ax.set_ylabel("Y轴标签")
ax.set_title("图表标题")
plt.show() # 此处会导致doctest阻塞当 sphinx-build -b doctest . _build 命令执行到 your_module.plot_numbers_problematic(x_data) 这一行时,plt.show() 会弹出一个图形窗口,并暂停 doctest 的执行,直到该窗口被手动关闭。
解决此问题的关键在于将Matplotlib函数的绘图逻辑与图形显示行为进行解耦。一个设计良好的Matplotlib绘图函数不应该自行决定何时显示图形,而应该将这一控制权交给调用者。这样,函数可以更灵活地应用于各种场景,包括:
实现这一解耦的常用方法是让绘图函数接受一个可选的 Axes 对象作为参数。如果提供了 Axes 对象,函数就在其上绘图;如果没有提供,函数则自行创建一个新的 Figure 和 Axes。最重要的是,从函数内部移除 plt.show() 的调用。
以下是优化后的 plot_numbers 函数示例:
import matplotlib.pyplot as plt
def plot_numbers(x, *, ax=None):
"""
显示一组数字的折线图。
参数
----------
x : list
要绘制的数字列表。
ax : matplotlib.axes.Axes, 可选
用于绘制数字的Matplotlib Axes对象。如果未提供,将创建一个新的Figure和Axes。
返回
-------
matplotlib.axes.Axes
绘制了数据的Axes对象。
示例
-------
>>> import your_module # 假设此函数在your_module中
>>> x_data = [1, 2, 5, 6, 8.1, 7, 10.5, 12]
>>> # 在doctest中,这不会打开窗口。
>>> # 如果需要在交互式环境中显示,请在调用函数后手动调用 plt.show()。
>>> ax_result = your_module.plot_numbers(x_data)
>>> # plt.show() # 在需要显示图形时取消注释
>>> # 验证 Axes 对象是否已创建
>>> assert isinstance(ax_result, plt.Axes)
>>> # 进一步的doctest可以检查ax_result的属性,例如标题、标签等
>>> ax_result.get_title()
'图表标题'
"""
if ax is None:
# 如果没有提供Axes,则创建一个新的Figure和Axes
_, ax = plt.subplots()
# 在提供的或新创建的Axes上进行绘图
ax.plot(x, marker="o", mfc="red", mec="red")
ax.set_xlabel("X轴标签")
ax.set_ylabel("Y轴标签")
ax.set_title("图表标题")
return ax # 返回Axes对象,以便调用者进行进一步操作关键改动点说明:
通过上述优化,当 doctest 运行时,它会执行 your_module.plot_numbers(x_data),函数会正常执行绘图逻辑并返回一个 Axes 对象,但不会弹出图形窗口,从而避免了阻塞。
这种方法带来了多方面的好处:
谁来调用 plt.show()? 在优化后的设计中,plt.show() 应该由最终的用户代码或顶层脚本来调用,而不是由库函数内部调用。例如:
import matplotlib.pyplot as plt
# 假设 plot_numbers 是优化后的函数
import your_module
if __name__ == "__main__":
x_data = [1, 3, 2, 4, 5]
# 在新的Figure和Axes上绘图并显示
ax1 = your_module.plot_numbers(x_data)
ax1.set_title("第一个图")
# 在同一个Figure上创建另一个Axes,并用另一个函数绘图
fig, (ax2, ax3) = plt.subplots(1, 2)
your_module.plot_numbers([5, 4, 3, 2, 1], ax=ax2)
ax2.set_title("第二个图")
your_module.plot_numbers([10, 8, 6, 4, 2], ax=ax3)
ax3.set_title("第三个图")
plt.tight_layout() # 调整子图布局
plt.show() # 在这里统一显示所有图形文档清晰度: 在函数的文档字符串中明确说明 ax 参数的作用,以及函数不会自行调用 plt.show()。
参考官方指南: Matplotlib官方文档也推荐了这种编写辅助函数的方式,例如在其“Making a helper functions”部分(https://www.php.cn/link/dda4087216e15d1784efc310005dd683)中有所提及。虽然该示例可能要求 ax 参数是强制的,但核心思想是相同的:将 Axes 对象作为参数传递。
通过将Matplotlib绘图函数的职责限制在纯粹的绘图操作上,并移除内部的 plt.show() 调用,我们可以有效地解决Sphinx doctest 在遇到交互式图形窗口时的阻塞问题。这种设计模式不仅使测试自动化成为可能,还显著提高了绘图函数的灵活性、可复用性和API的清晰度,是编写高质量Matplotlib库代码的重要实践。开发者应养成习惯,让绘图函数返回 Axes 对象,并将图形的显示或保存逻辑留给调用者处理。
以上就是Sphinx Doctest与Matplotlib绘图:避免交互式阻塞的策略的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号