编码指南#

我们感谢遵循这些指南,因为这提高了代码库的可读性、一致性和可维护性。

API 指南

如果添加新功能、更改行为或函数签名,或删除公共接口,请咨询 API 指南

PEP8,由 flake8 强制执行#

格式化应遵循 PEP8 的建议,由 flake8 强制执行。Matplotlib 修改了 PEP8,将最大行长度扩展到 88 个字符。你可以通过命令行检查 flake8 的合规性,使用

python -m pip install flake8
flake8 /path/to/module.py

或者您的编辑器可能提供与它的集成。请注意,Matplotlib 故意不使用 black 自动格式化工具(1),特别是由于其无法理解数学表达式的语义(23)。

包导入#

使用标准的 scipy 约定导入以下模块:

import numpy as np
import numpy.ma as ma
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.cbook as cbook
import matplotlib.patches as mpatches

通常,Matplotlib 模块不应使用 from matplotlib import rcParams 导入 rcParams,而应将其作为 mpl.rcParams 访问。这是因为某些模块在 rcParams 单例构造之前就被导入。

变量名#

在可行的情况下,请使用我们内部的对象命名约定来命名给定类的对象及其任何子类的对象:

基类

变量

多重

FigureBase

fig

Axes

ax

Transform

trans

trans_<source>_<target>

trans_<source> 当目标为屏幕时

通常,通过在变量名称后添加后缀来表示同一类的多个实例。如果表格中未指定格式,请根据需要使用数字或字母。

类型提示#

如果你添加了新的公共API或更改了公共API,请更新或添加相应的 mypy 类型提示。我们通常使用 存根文件 (*.pyi) 来存储类型信息;例如 colors.pyi 包含了 colors.py 的类型信息。一个值得注意的例外是 pyplot.py,它的类型提示是内联的。

类型提示可以通过 stubtest 工具进行验证,该工具可以使用 tox -e stubtest 在本地运行,并且是 自动化测试 套件的一部分。现有函数的类型提示也会通过 mypy 的 预提交钩子 进行检查。

新模块和文件:安装#

  • 如果你添加了新的文件或目录,或者重新组织了现有的文件,请确保新文件包含在相应目录的 meson.build 中。

  • 新模块 可以 以内联方式或使用类似现有模块的并行存根文件进行类型标注。

C/C++ 扩展#

  • 扩展可以用 C 或 C++ 编写。

  • 代码风格应符合 PEP7(理解到 PEP7 并未涉及 C++,但其大部分建议仍然适用)。

  • Python/C 接口代码应与核心 C/C++ 代码分开。接口代码应命名为 FOO_wrap.cppFOO_wrapper.cpp

  • 头文件文档(即文档字符串)应采用 Numpydoc 格式。我们不打算使用自动化工具来处理这些文档字符串,而 Numpydoc 格式在科学 Python 社区中被广泛理解。

  • extern/ 目录中的 C/C++ 代码是外部引入的,应尽可能保持与上游版本一致。只有在代码库的其他部分无法进行所需更改时,才允许修改它以修复错误或实现新功能。特别是,应避免对其进行风格修复。

关键字参数处理#

Matplotlib 广泛使用 **kwargs 来实现从一个函数到另一个函数的透传自定义。一个典型的例子是 textmatplotlib.pyplot.text 的定义是对 matplotlib.axes.Axes.text 的简单透传:

# in pyplot.py
def text(x, y, s, fontdict=None, **kwargs):
    return gca().text(x, y, s, fontdict=fontdict, **kwargs)

matplotlib.axes.Axes.text (为说明简化) 只是将所有 argskwargs 传递给 matplotlib.text.Text.__init__:

# in axes/_axes.py
def text(self, x, y, s, fontdict=None, **kwargs):
    t = Text(x=x, y=y, text=s, **kwargs)

``matplotlib.text.Text.__init__``(再次简化)只是将它们传递给 matplotlib.artist.Artist.update 方法:

# in text.py
def __init__(self, x=0, y=0, text='', **kwargs):
    super().__init__()
    self.update(kwargs)

update 的工作是查找名为 set_property 的方法,如果 property 是一个关键字参数。也就是说,没有人查看关键字,它们只是通过API传递给艺术家构造函数,该构造函数查找适当命名的方法并以值调用它们。

一般来说,**kwargs 的使用应保留给传递关键字参数的情况,如上例所示。如果所有关键字参数都将在函数中使用,而不是传递下去,那么在函数定义中使用键/值关键字参数,而不是 **kwargs 惯用法。

在某些情况下,您可能希望在本地函数中使用某些键,并让其他键传递下去。与其从 **kwargs 中弹出参数来使用,不如将它们指定为本地函数的仅关键字参数。这使得一眼就能看出函数中将使用哪些参数。例如,在 plot() 中,scalexscaley 是本地参数,其余的作为 Line2D() 关键字参数传递:

# in axes/_axes.py
def plot(self, *args, scalex=True, scaley=True, **kwargs):
    lines = []
    for line in self._get_lines(*args, **kwargs):
        self.add_line(line)
        lines.append(line)

使用日志记录调试信息#

Matplotlib 使用标准的 Python logging 库来写入详细的警告、信息和调试消息。请使用它!在你所有使用 print 调用进行调试的地方,尝试改为使用 logging.debug

要在你的模块中包含 logging,在模块的顶部,你需要 import logging。然后在你的代码中调用如下:

_log = logging.getLogger(__name__)  # right after the imports

# code
# more code
_log.info('Here is some information')
_log.debug('Here is some more detailed information')

将记录到名为 matplotlib.yourmodulename 的记录器。

如果 Matplotlib 的终端用户在其代码中使用 Matplotlib 提供的辅助工具将 logging 设置为显示比 logging.WARNING 更详细的级别:

plt.set_loglevel("debug")

或手动使用

import logging
logging.basicConfig(level=logging.DEBUG)
import matplotlib.pyplot as plt

然后他们会收到类似的消息

DEBUG:matplotlib.backends:backend MacOSX version unknown
DEBUG:matplotlib.yourmodulename:Here is some information
DEBUG:matplotlib.yourmodulename:Here is some more detailed information

避免使用预计算字符串(f-stringsstr.format``等)进行日志记录,因为这会带来安全性和性能问题,并且它们会干扰样式处理程序。例如,使用 ``_log.error('hello %s', 'world') 而不是 _log.error('hello {}'.format('world'))_log.error(f'hello {s}')

使用哪个日志级别?#

你可以从五个层级发出消息。

  • logging.criticallogging.error 主要用于那些会终止库的使用但不会导致解释器崩溃的错误。

  • logging.warning_api.warn_external 用于向用户发出警告,见下文。

  • logging.info 用于记录用户在程序行为异常时可能想知道的信息。默认情况下,这些信息不会显示。例如,如果因为对象的位置是 NaN 而没有绘制对象,这通常可以忽略,但困惑的用户可以调用 logging.basicConfig(level=logging.INFO) 并获得一条解释原因的错误消息。

  • logging.debug 是最不可能显示的,因此可以是最详细的。“预期的”代码路径(例如,报告布局或渲染的正常中间步骤)应仅在此级别记录。

默认情况下,logging 会将所有级别高于 logging.WARNING 的日志消息显示到 sys.stderr

logging 教程 建议 logging.warning_api.warn_external`(使用 `warnings.warn)的区别在于,_api.warn_external 应用于用户必须更改以停止警告的内容(通常在源代码中),而 logging.warning 可以更持久。此外,请注意 _api.warn_external 默认情况下只会为每行用户代码发出一次给定的警告,而 logging.warning 每次调用时都会显示消息。

默认情况下,warnings.warn 会显示包含 warn 调用的代码行。这通常并不比警告信息本身更具信息性。因此,Matplotlib 使用 _api.warn_external,它使用 warnings.warn,但会向上遍历堆栈并显示 Matplotlib 之外的第一行代码。例如,对于模块:

# in my_matplotlib_module.py
import warnings

def set_range(bottom, top):
    if bottom == top:
        warnings.warn('Attempting to set identical bottom==top')

运行脚本:

from matplotlib import my_matplotlib_module
my_matplotlib_module.set_range(0, 0)  # set range

将显示

UserWarning: Attempting to set identical bottom==top
warnings.warn('Attempting to set identical bottom==top')

修改模块以使用 _api.warn_external:

from matplotlib import _api

def set_range(bottom, top):
    if bottom == top:
        _api.warn_external('Attempting to set identical bottom==top')

并且运行相同的脚本将会显示

UserWarning: Attempting to set identical bottom==top
my_matplotlib_module.set_range(0, 0)  # set range

贡献代码的许可证#

Matplotlib 仅使用 BSD 兼容代码。如果您从另一个项目中引入代码,请确保其具有 PSF、BSD、MIT 或兼容许可证(有关各个许可证的详细信息,请参阅开源倡议的 许可证页面)。如果没有,您可以考虑联系作者并请求他们重新许可。GPL 和 LGPL 代码在主代码库中是不可接受的,尽管我们正在考虑通过单独的渠道分发 L/GPL 代码的替代方法,可能是通过工具包。如果您包含代码,请确保如果代码的许可证要求您随代码分发许可证,则在许可证目录中包含该代码许可证的副本。非 BSD 兼容许可证在 Matplotlib 工具包(例如,basemap)中是可接受的,但请确保您清楚地声明所使用的许可证。

为什么兼容BSD?#

在开源世界中,两种主要的许可证变体是 GPL 风格和 BSD 风格。还有无数其他许可证对代码重用施加了特定的限制,但在 GPL 和 BSD 变体之间有一个重要的区别需要考虑。最著名且可能最广泛使用的许可证是 GPL,它不仅授予你对源代码的全部权利,包括重新分发,还附带了一个额外的义务。如果你在自己的代码中使用 GPL 代码,或与之链接,你的产品必须以 GPL 兼容的许可证发布。也就是说,你必须向其他人提供源代码,并赋予他们重新分发的权利。许多最著名且广泛使用的开源项目都是以 GPL 发布的,包括 Linux、GCC、Emacs 和 Sage。

第二大类是BSD风格的许可证(包括MIT和Python PSF许可证)。这些许可证基本上允许你对代码做任何你想做的事情:忽略它,将其包含在你自己的开源项目中,将其包含在你的专有产品中,出售它,等等。Python本身是根据BSD兼容的许可证发布的,从PSF许可证页面引用如下:

There is no GPL-like "copyleft" restriction. Distributing
binary-only versions of Python, modified or not, is allowed. There
is no requirement to release any of your source code. You can also
write extension modules for Python and provide them only in binary
form.

在最后一段的宽松意义上,以BSD风格许可证发布的著名项目包括BSD操作系统、Python和TeX。

早期 Matplotlib 开发者选择 BSD 兼容许可证有几个原因。Matplotlib 是一个 Python 扩展,我们选择了一个基于 Python 许可证(BSD 兼容)的许可证。此外,我们希望吸引尽可能多的用户和开发者,许多软件公司不会在他们计划分发的软件中使用 GPL 代码,即使是那些高度致力于开源开发的公司,如 enthought,出于对 GPL 病毒性传播其代码库的合法担忧。实际上,他们希望保留发布某些专有代码的权利。使用 Matplotlib 的公司和机构通常会做出重大贡献,因为他们有资源完成工作,即使是枯燥的工作。Matplotlib 的两个后端(FLTK 和 WX)是由私营公司贡献的。许可证选择的最后一个原因是与其他科学计算 Python 扩展的兼容性:ipython、numpy、scipy、enthought 工具套件和 Python 本身都是基于 BSD 兼容许可证分发的。