编码规范#

我们感谢遵循这些准则,因为它提高了代码库的可读性,一致性和可维护性.

API 指南

如果添加新功能,更改行为或函数签名,或者删除公共接口,请查阅 API 指南 .

PEP8,由 flake8 强制执行#

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

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

或者您的编辑器可以提供与它的集成.请注意,Matplotlib 有意不使用 black 自动格式化程序(1),特别是由于它无法理解数学表达式的语义(2, 3).

包导入#

使用标准的 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 类型提示. 我们通常使用 stub files ( *.pyi ) 来存储类型信息;例如 colors.pyi 包含 colors.py 的类型信息.一个值得注意的例外是 pyplot.py ,它的类型提示是内联的.

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

新模块和文件:安装#

  • 如果您添加了新的文件或目录,或者重组了现有的文件或目录,请确保新的文件包含在相应目录的 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 来实现从一个函数到另一个函数的传递式自定义.一个典型的例子是 text . matplotlib.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)

如果 property 是一个关键字参数, update 就会查找名为 set_property 的方法.也就是说,没有人查看关键字,它们只是通过 API 传递给 artist 构造函数,后者查找适当命名的方法并使用该值调用它们.

As a general rule, the use of **kwargs should be reserved for pass-through keyword arguments, as in the example above. If all the keyword args are to be used in the function, and not passed on, use the key/value keyword args in the function definition rather than the **kwargs idiom.

在某些情况下,您可能想在本地函数中使用一些键,而让其他键传递下去.与其从 *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 的 logger 中.

如果 Matplotlib 的最终用户设置 logging 以比 logging.WARNING 更详细的级别显示,可以使用 Matplotlib 提供的辅助函数:

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-strings , str.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 tutorial 建议 logging.warning_api.warn_external (使用 warnings.warn )之间的区别在于, _api.warn_external 应该用于用户必须更改以停止警告的事情(通常在源代码中),而 logging.warning 可以更持久.此外,请注意,默认情况下, _api.warn_external 对于用户代码的每一行只发出一次给定的警告,而 logging.warning 每次调用都会显示该消息.

默认情况下, warnings.warn 显示具有 warn 调用的代码行.这通常不比警告消息本身更informative.因此,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 或兼容的许可证(有关各个许可证的详细信息,请参阅开放源代码倡议 licenses page ). 如果没有,你可以考虑联系作者并要求他们重新授权. 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 兼容的许可证分发.