贡献指南#
重要
AI 使用政策:在提交任何代码或文档之前,请务必查阅仓库 CONTRIBUTING.md 文件中列出的 AI 使用政策。
感谢你抽出时间参与贡献!MNE-Python 是一个主要依靠志愿者投入维持的开源项目。只要遵守我们的行为准则,我们欢迎任何人参与贡献。
参与贡献的方式有很多,例如:
使用本软件,发现 bug 时告诉我们!我们只能修复已知的 bug。
在我们的用户论坛上回答问题。
告诉我们文档中让你感到困惑或不清晰的地方。
告诉我们你希望 MNE-Python 能做哪些事,或者它已能做但你认为可以更简便的功能。
改善我们网站的无障碍访问性。
修复 bug。
修正函数文档字符串中的错误。
实现新功能。
改进现有教程或编写新教程。
为 MNE-Python 依赖的众多 Python 包之一做贡献。
要报告 bug、请求新功能,或询问令人困惑的文档,通常最好先在我们的用户论坛上开一个新帖;这样你很可能最快得到帮助,也有助于让我们的 GitHub issue 跟踪器专注于那些我们确信需要修改软件的问题(而不是可以在用户代码中解决的问题)。我们最终可能会请你也到 GitHub 上开一个 issue,但从论坛开始有助于我们保持条理。为了最快获得结果,请务必包含你的操作系统和 MNE-Python 版本信息,并在适用时附上一个尽可能简短的可复现代码示例,最好使用我们的某个示例数据集。
如果你想修复 bug、添加新功能,或改进我们的文档字符串/教程/网站,这类贡献需要通过我们的 GitHub 仓库完成。本页余下部分将说明如何搭建工作流程,让通过 GitHub 参与贡献尽可能轻松。
Want an example to work through?
你也可以直接读完本页其余内容;如果你更习惯“边做边学”,可以看看我们标记为“easy”的 GitHub issue,挑一个感兴趣的,一边阅读本指南一边完成它!
贡献流程概览#
注意
提醒:所有贡献者都应遵守我们的行为准则。
对 MNE-Python 的修改通常是这样完成的:先派生(fork)MNE-Python 仓库,再在你的 fork 上进行修改(通常是将 fork 克隆到个人电脑,在本地修改,然后把本地修改推送到 GitHub 上的 fork),最后创建一个拉取请求,将你的修改合并回共享的“上游”(upstream)代码库版本。
一般而言,你会与 MNE-Python 代码库的三个不同副本打交道:位于 mne-tools/mne-python 的官方远程副本(通常称为 upstream)、上游仓库在你账号下的远程 fork(URL 类似,只是把 mne-tools 换成你的用户名,通常称为 origin),以及你电脑上的本地副本。典型的贡献流程是:
将本地副本与 upstream 同步
在本地副本上进行修改
将修改推送到
origin(你账号下上游仓库的远程 fork)从你的 fork 向上游提交拉取请求
下文“git 基础命令”和“GitHub 工作流程”两节将更详细地介绍该过程。
搭建本地开发环境#
配置 git#
为参与贡献做好准备,请确认本地电脑已安装 git:
在 Linux 上,命令
sudo apt install git通常就足够了;更多选择请参阅官方 Linux 说明。在 MacOS 上,请下载
.dmg安装程序;Atlassian 也提供了更详细的说明以及 MacPorts 或 Homebrew 等替代方案。在 Windows 上,请下载并安装 git for Windows。它通过 Git BASH 提供了自己的 shell,其中包含许多与 Linux 等价、对开发很有用的命令行程序。
Windows 10 还提供 Windows Subsystem for Linux(WSL),其功能与 git BASH 类似,但 MNE-Python 开发者尚未广泛测试,在图形输出(例如构建文档)方面仍可能存在问题
安装 git 后,唯一绝对必要的配置步骤是设置你的身份和联系方式:
$ git config --global user.name "Your Name"
$ git config --global user.email you@yourdomain.example.com
请确保 GitHub 账户与本地 git 配置使用同一个邮箱地址。一个 GitHub 账户可以关联多个邮箱,因此如果你最初设置时用了不同的邮箱,可以把本地邮箱添加到 GitHub 账户中。
git 迟早会问你希望用哪个文本编辑器编写提交信息,不如现在就配置好:
$ git config --global core.editor emacs # or vim, or nano, or subl, or...
还有许多其他自定义 git 行为的方式;更多信息请参阅配置 git。
GNU Make#
我们使用 GNU Make 来组织开发中常用的命令或短脚本。它们保存在名为 Makefile 的文件中。MNE-Python 有两个 Makefile:一个位于包的根目录(主要包含测试命令),另一个位于 doc/(包含以不同方式构建文档页面的配方)。
要检查是否已安装 make,请输入
$ make
在终端中,你应该会看到
make: *** No targets specified and no makefile found. Stop.
如果你没有看到这个或类似输出,可能尚未安装 make。
如果你看到:
bash: make: command not found
为 git BASH(随 git for Windows 提供)安装 make:
从 ezwinports 下载
make-newest.version-without-guile-w32-bin.zip解压 zip 文件夹
将内容复制到
path_to_git\mingw64\(例如与其中已有的对应文件夹合并)首次使用 git BASH 时,需要运行一次(以便能够激活
mnedev环境):$ conda init bash
如果你反而看到类似这样的错误:
bash: conda: command not found
出现在 git BASH 窗口顶部,你需要先把以下内容添加到
path_to_Anacondapath_to_Anaconda\Scripts
Windows-PATH 中。
派生(fork)MNE-Python 仓库#
安装并配置好 git 后,在创建代码库的本地副本之前,请前往 MNE-Python 的 GitHub 页面,并在你的 GitHub 用户账户下创建一个 fork。
这会在你的 GitHub 用户账户中创建 MNE-Python 代码库的一个副本(称为“你的 fork”)。你对 MNE-Python 所做的修改最终会被“推送”到你的 fork,并通过“拉取请求”并入 MNE-Python 的官方版本(通常称为“上游版本”)。下文将详细介绍这一过程;此处概述该结构是如何建立的:
创建虚拟环境#
这些说明会创建一个与系统级 Python 以及电脑上其他受管理的 Python 环境相隔离的 Python 环境。这样你可以在不同 Python 版本之间切换,也可以在 MNE-Python 的稳定版和开发版之间切换(例如,用同一台电脑既以稳定版分析数据,又使用最新的开发版修复 bug 或添加新功能)。即使你已经按照稳定版 MNE-Python 的安装说明操作过,现在也应重复该过程,为 MNE-Python 开发创建一个新的独立环境(这里将其命名为 mnedev):
$ curl --remote-name https://raw.githubusercontent.com/mne-tools/mne-python/main/environment.yml
$ conda env create --file environment.yml --name mnedev
$ conda activate mnedev
现在你有两个 MNE-Python 环境:mne(或你安装稳定版 MNE-Python 时使用的自定义名称)和我们刚创建的 mnedev。此时 mnedev 中也有 MNE-Python 稳定版(environment.yml 文件安装的就是它),但接下来我们要从 mnedev 中移除稳定版,并用开发版替换。为此,我们将从你的远程 fork 克隆 MNE-Python 仓库,并把本地副本连接到代码库的上游版本,以便及时跟进其他贡献者的修改。首先,根据你的情况编辑以下两个变量:
$ GITHUB_USERNAME="insert_your_actual_GitHub_username_here"
$ # pick where to put your local copy of MNE-Python development version:
$ INSTALL_LOCATION="/opt"
注意
在 Windows 上,请在变量名之前加上 set(如 set GITHUB_USERNAME=... 等)。
然后对你的远程 fork(origin)做本地克隆:
$ cd $INSTALL_LOCATION
$ git clone https://github.com/$GITHUB_USERNAME/mne-python.git
最后,在本地克隆与官方仓库(upstream)之间建立链接,并设置 git diff 使其正常工作:
$ cd mne-python
$ git remote add upstream https://github.com/mne-tools/mne-python.git
$ git fetch --all
$ git config --local blame.ignoreRevsFile .git-blame-ignore-revs
现在我们将移除 MNE-Python 稳定版,并用开发版(即刚才用 git 创建的克隆)替换。请先确认处于正确的环境(conda activate mnedev),然后执行:
$ cd $INSTALL_LOCATION/mne-python # make sure we're in the right folder
$ conda remove --force mne-base # the --force avoids dependency checking
$ pip install -e .
命令 pip install -e . 通过创建指向源代码目录的链接,将 Python 模块安装到当前环境(而不是像通常那样把代码复制到 pip 的 site_packages 目录)。这意味着你对 MNE-Python 源代码所做的任何修改,都会在你下次打开 Python 解释器并 import mne 时生效(pip 的 -e 标志表示“可编辑”安装)。
最后,我们再添加几个依赖,它们不是运行 MNE-Python 所必需的,但本地运行测试套件需要:
$ pip install --group=test
以及构建文档所需的依赖:
$ pip install --group=doc
$ conda install graphviz
注意
在 Windows 上,如果你已用上面的 conda 命令安装 graphviz,却仍遇到类似这样的错误:
WARNING: dot command 'dot' cannot be run (needed for graphviz output), check the graphviz_dot setting
请尝试将 graphviz 文件夹添加到 PATH:
$ PATH=$CONDA_PREFIX\\Library\\bin\\graphviz:$PATH
构建文档还需要 optipng:
在 Linux 上,使用命令
sudo apt install optipng。在 MacOS 上,可以使用 Homebrew 安装
optipng。在 Windows 上,从 optipng for Windows 压缩包中解压
optipng.exe到doc/文件夹。对 Windows 用户而言此步骤是可选的。
运行各种测试还需要一些额外的可选依赖,例如解码测试需要 scikit-learn,MRI 测试需要 nibabel。如果你想运行所有测试,可以考虑使用我们的 MNE 安装程序(其中提供了这些依赖),或者留意 pytest 报告的跳过项并安装相关库。例如,下面的 traceback:
SKIPPED [2] mne/io/eyelink/tests/test_eyelink.py:14: could not import 'pandas': No module named 'pandas'
表明需要安装 pandas 才能运行 Eyelink 测试。
git 基础命令#
学习使用 git 可能需要很长时间,因为它是一个复杂而强大的工具,用于在多个用户之间管理文件版本,而每个用户又拥有代码库的多个副本。在上面的设置命令中,我们已经看到了几个对 MNE-Python 开发者有用的 git 基础命令:
git clone <URL_OF_REMOTE_REPO>(创建仓库的本地副本)git remote add <NICKNAME_OF_REMOTE> <URL_OF_REMOTE_REPO>(将本地副本连接到另一个远程仓库)git fetch --all(获取已连接远程仓库的当前状态)
你无疑还会用到与分支(branch)相关的其他命令。分支表示本地克隆或远程仓库中代码库的多个副本。分支通常用于试验新功能,同时保留一份干净、可用的原始代码库副本,以便随时切回。任何仓库的默认分支都称为 main,建议将 main 分支保留为上游工作代码库的那份干净副本。因此,如果你想添加新功能,应先让本地 main 分支与上游仓库同步,然后基于 main 创建一个新分支并检出它,这样你所做的任何修改都会存在于该新分支上(而不是 main 上):
$ git checkout main # switch to local main branch
$ git fetch upstream # get the current state of the remote upstream repo
$ git merge upstream/main # synchronize local main branch with remote upstream main branch
$ git checkout -b new-feature-x # create local branch "new-feature-x" and check it out
现在你已在新分支上,可以修复 bug、添加新功能、编写测试、更新文档等。完成后,就该把修改整理成一系列提交(commit)。提交就像仓库的快照——实际上更像是描述从最近一次快照到当前快照需要做哪些更改的说明。
Git 知道人们常常同时修改多个文件中的多处内容,但最终应将这些修改按共同目标分组为彼此相关的变更集(这样同事更容易理解和审查)。例如,你可以把所有代码修改放在一个提交中,把新增的单元测试放在另一个提交中,把文档修改放在第三个提交中。Git 通过所谓的暂存区(stage,或称 staging area)实现这一点。对代码库做了一些修改后,你会得到 git 所称的“未暂存更改”(unstaged changes),它们会通过 status 命令显示出来:
$ git status # see what state the local copy of the codebase is in
这些未暂存的更改可以逐项加入暂存区:既可以添加整个文件的更改,也可以交互式地只添加某些行:
$ git add mne/some_file.py # add all the changes you made to this file
$ git add mne/some_new_file.py # add a completely new file in its entirety
$ # enter interactive staging mode, to add only portions of a file:
$ git add -p mne/viz/some_other_file.py
当所有相关修改都收集到暂存区后,git status 命令会将它们称为“已暂存待提交的更改”(changes staged for commit)。你可以用 commit 命令将它们提交到当前分支。如果只输入 git commit,git 会打开你配置的文本编辑器,让你编写提交信息——即对本提交中归组的修改的简短说明。你也可以用 -m 标志在命令行直接传入提交信息,从而跳过文本编辑器。例如,如果第一个提交添加了新功能,你的提交信息可以是:
$ git commit -m 'ENH: adds feature X to the Epochs class'
提交完成后,暂存区就空了,你可以重复这一循环,添加单元测试和文档修改:
$ git add mne/tests/some_testing_file.py
$ git commit -m 'add test of new feature X of the Epochs class'
$ git add -p mne/some_file.py mne/viz/some_other_file.py
$ git commit -m 'DOC: update Epochs and BaseEpochs docstrings'
$ git add tutorials/new_tutorial_file.py
$ git commit -m 'DOC: adds new tutorial about feature X'
完成后,就该运行测试套件,确认你的修改没有破坏任何现有功能,并确认新测试覆盖了你新增的代码行(参见下文的“运行测试套件”和“构建文档”)。一切正常后,就可以把修改推送到你的 fork:
$ # push local changes to remote branch origin/new-feature-x
$ # (this will create the remote branch if it doesn't already exist)
$ git push origin new-feature-x
最后,前往 MNE-Python 的 GitHub 页面,点击 pull requests 标签页,点击“new pull request”按钮,并选择“compare across forks”,将你的新分支(new-feature-x)选为“head repository”。有关创建拉取请求的更多信息,请参阅 GitHub 关于从 fork 创建 PR 的帮助页面。
如果推送修改前有任何测试失败,请尝试修复,然后添加并提交修复测试的修改,再推送到你的 fork。如果你卡住了、不知道如何修复测试,也可以照常把提交推送到 fork 并(按上述方式)创建拉取请求,然后在拉取请求中说明测试如何失败,并询问修复建议。
想进一步了解 git,请查看 GitHub 帮助网站、GitHub Skills 教程系列以及 Pro Git 书籍。
使用 SSH 连接 GitHub(可选)#
加快开发的一个简单方法是减少输入密码的次数。SSH(secure shell)允许使用预共享密钥对进行身份验证。密钥对中的私钥保存在你的电脑上并保密,公钥则添加到你的 GitHub 账户;当你从电脑连接 GitHub 时,本地 git 客户端会用本地私钥验证远程公钥,只有密钥匹配时才允许访问你的账户。GitHub 有多个帮助页面指导你完成这一过程。
设置好 GitHub 的 SSH 身份验证后,应将 MNE-Python GitHub 远程仓库的地址从 https:// 地址改为 git@ 地址,以便 git 通过 SSH 而非 HTTPS 连接。例如:
$ git remote -v # show existing remote addresses
$ git remote set-url origin git@github.com:$GITHUB_USERNAME/mne-python.git
$ git remote set-url upstream git@github.com:mne-tools/mne-python.git
MNE-Python 编码规范#
一般要求#
所有新功能都必须有测试覆盖#
例如,在 mne/evoked.py 中为 mne.Evoked 新增的方法,应在 mne/tests/test_evoked.py 中有对应的测试。
所有新功能都必须有文档#
这包括为所有公共 API 变更提供完整的文档字符串说明,以及为主要贡献提供操作示例或更长的教程。私有函数的文档字符串可以更简略,但通常不应省略。
尽可能避免 API 变更#
对公共 API 的修改(例如类/函数/方法名和签名)不应轻率进行,因为它们可能破坏用户现有的脚本。API 变更需要一个弃用周期(附带警告),让用户有时间在 API 变更成为默认行为之前调整代码。相关说明请参阅弃用(deprecation)一节和 mne.utils.deprecated。bug 修复(即某些功能没有做到其声称之事)不需要弃用周期。
请注意,任何新的 API 元素都应添加到主参考中;类、函数、方法和属性只有包含在 Python API Reference(doc/python_reference.rst)中才能被交叉引用。
使用装饰器或警告进行弃用#
MNE-Python 提供了一个 deprecated() 装饰器,用于将来版本中会被移除的类和函数:
from mne.utils import deprecated
@deprecated('my_function is deprecated and will be removed in 0.XX; please '
'use my_new_function instead.')
def my_function():
return 'foo'
如果需要弃用一个参数,请使用 mne.utils.warn()。例如,要把参数从 old_param 重命名为 new_param,可以这样做:
from mne.utils import warn
def my_other_function(new_param=None, old_param=None):
if old_param is not None:
depr_message = ('old_param is deprecated and will be replaced by '
'new_param in 0.XX.')
if new_param is None:
new_param = old_param
warn(depr_message, FutureWarning)
else:
warn(depr_message + ' Since you passed values for both '
'old_param and new_param, old_param will be ignored.',
FutureWarning)
# Do whatever you have to do with new_param
return 'foo'
进行弃用时,还应向相关测试文件添加相应的测试,以确保警告在你预期的条件下发出:
# test deprecation warning for function
with pytest.warns(FutureWarning, match='my_function is deprecated'):
my_function()
# test deprecation warning for parameter
with pytest.warns(FutureWarning, match='values for both old_param'):
my_other_function(new_param=1, old_param=2)
with pytest.warns(FutureWarning, match='old_param is deprecated and'):
my_other_function(old_param=2)
还应搜索代码库中内部使用该弃用函数或参数的所有情况,并立即更新(不要等到弃用周期结束才做)。之后,在弃用期结束、准备所声明的发布版本时:
删除已弃用的函数
移除已弃用的参数(以及
my_other_function中处理old_param是否存在的条件分支)移除弃用测试
再次检查是否还有其他测试依赖已弃用的函数或参数,如有则将其更新为使用新函数/参数。
在变更日志中说明你的修改#
在你的变更集中加入简短的修改说明,它会以 towncrier 格式出现在变更日志(changelog)中。该机制会汇总命名规范的小型 .rst 文件来生成变更日志。对于非常小的修改(例如修正文档中的拼写错误)可以跳过此步骤。
根据变更类型,共有六个独立的变更小节。要向某个小节添加变更日志条目,请将其命名为 doc/changes/dev/<PR-number>.<type>.rst。类型包括:
- notable
- 用于全局性变更,例如在整个包范围内添加类型提示。此类变更很少见。
- dependency
- 用于依赖变更,例如新增依赖或更改现有依赖的最低版本。
- bugfix
- 用于 bug 修复。可以在没有弃用期的情况下改变代码行为。
- apichange
- 对现有代码行为的变更,需要弃用期。只有在同时改变现有代码行为时,才可以包含新增功能。
- newfeature
- 用于不改变现有代码行为的新功能。如果在添加这些功能的同时改变了现有代码行为,则应改为标记为
apichange。 - other
- 用于不属于上述任何类别的变更,例如内部重构。
例如,对于编号为 12345 的新功能 PR,变更日志条目应作为新文件 doc/changes/dev/12345.newfeature.rst 添加。该文件应包含:
变更的简短说明,通常为一句话或两句话的单行。
指向公共 API 端点(如函数(
:func:)、类(:class:)和方法(:meth:))的 reST 链接。如果变更仅涉及私有函数/属性的内部实现,请说明进行了内部重构,而不要列出所更改的私有属性名称。作者署名。如果你是新贡献者(非常高兴你的加入!🤗),应使用
:newcontrib:reST 角色;此前的贡献者则应使用指向其姓名的标准 reST 链接。例如,新贡献者可以写:Short description of the changes, by :newcontrib:`Firstname Lastname`.
而此前的贡献者可以写:
Short description of the changes, by `Firstname Lastname`_.
请确保你的姓名已包含在 doc/changes/names.inc 的作者列表中,否则拉取请求上的变更日志检查(以及之后的文档构建)将会失败。要添加作者姓名,请按以下模式追加一行(注意其语法与变更日志中使用的不同):
.. _Your Name: https://www.your-website.com/
许多贡献者选择用这种方式链接到自己的 GitHub 主页。可以看看该文件中已有的条目以获取灵感。
有时,本应作为单条变更日志条目的修改会分散在多个 PR 中。此时,请编辑相关变更的现有 towncrier 文件,并用 :gh: 角色在括号中追加其他 PR 编号,如下所示:
Short description of the changes, by `Firstname Lastname`_. (:gh:`12346`)
接手长期停滞的拉取请求#
如果某个拉取请求已被放弃,其他人可以接手。通常,当数周没有提交、且原作者未表示计划稍后继续时,就认为该 PR 已被放弃。此时,请先在 PR 的评论中提醒原作者,询问其是否打算继续。如果原作者回复不打算继续,或至少 2 周后仍无回复,你就可以接手该 PR。
要在原作者已有工作的基础上继续,你可以将他们当时工作的分支副本添加到你的 fork:
git remote add original-author https://github.com/original-author/mne-python.git
git fetch original-author
git checkout -b some-new-feature original-author/some-new-feature
随后你可以继续向 fork 上的这个新分支推送提交,并从该分支创建 PR。
在新 PR 的描述中引用原 PR 编号(例如“Closes #12345 (supersedes)”),并保留指向对应 issue 的链接(例如“Fixes #12340”)。
在 PR 合并之前,请确保原作者因其已有工作而获得署名。要在文档中署名,请将其姓名添加到 doc/changes/names.inc(如果尚未包含),并在变更日志条目中将其列为作者,例如:
Short description of the changes, by `Your Name`_ and `Original Author Name`_.
要在代码中为原作者署名,请将其姓名和邮箱作为共同作者添加到至少一条提交信息的末尾(例如添加变更日志条目的那次提交):
Your commit message
Co-authored-by: Original Author Name <original-author-email@example.com>
持续集成(CI)与提交 PR 前的本地测试#
持续集成(CI)使用自动化系统在你创建或更新拉取请求时对代码运行测试和检查。MNE-Python 使用持续集成(CI)来保证代码质量、跨多个平台测试并自动验证拉取请求。然而,CI 运行比本地测试慢,其中一些还需要付费。因此,不要依赖 CI 为你发现 bug 和风格错误;在创建新 PR 之前以及每次向已打开的 PR 推送新修改之前,请改为在本地运行测试。
MNE-Python 使用以下 CI 服务:
GitHub Actions 和 Azure,用于跨平台测试代码
CodeCov,用于检查代码的测试覆盖率
CircleCI,用于构建文档
如果你是首次贡献,可能会注意到 GitHub Actions 任务不会自动启动,或显示为“pending”。这是预期行为。对于新贡献者提交的任务,GitHub Actions 需要维护者手动批准后才会运行。这是一项防止滥用 CI 资源的安全措施。
因此:
GitHub Actions 任务可能保持 pending 或未运行状态
这并不表示你的代码有问题
一旦你至少有一个 PR 被合并到 MNE-Python 仓库,之后的贡献就不再需要手动批准。
除非 PR 最近一次提交所关联的 GitHub 账户绑定了 CircleCI 账户,否则 CircleCI 不会构建文档。创建 CircleCI 账户简单且免费,在 CircleCI 上选择“login with GitHub”即可开始。如果不这样做,它就会显示为失败的 CI 任务。
在创建或更新 PR 之前,请在本地检查:
所有测试都通过(参见“运行测试套件”)
如果你的修改影响文档,文档构建无错误(参见“构建文档”)
风格检查通过(参见 PEP8,由
ruff强制执行)
让测试既快速又全面#
编写测试时,请尽可能使用 testing 数据集而不是示例数据集;它包含大多数 MNE-Python 对象的小型版本(例如持续时间短、通道数少的 Raw 对象)。你还可以检查测试漏掉了哪些行,然后修改现有测试(或编写新测试)以覆盖这些漏掉的行。下面是一个示例,报告运行 test_evoked.py 和 test_topo.py 时 mne.viz 中漏掉了哪些行:
$ pytest --cov=mne.viz --cov-report=term-missing mne/viz/tests/test_evoked.py mne/viz/tests/test_topo.py
还可以使用 pytest --durations=5 来确保新增或修改的测试不会过多拖慢测试套件。
代码风格#
遵循标准 Python 风格指南#
所有对 MNE-Python 的贡献都会按照 PEP 8 中描述的风格指南进行检查。我们还会检查常见的编码错误(例如定义了但从未使用的变量)。我们对这些指南允许的例外极少,并使用 ruff 等工具自动检查代码风格。在 mne-python 根目录下,可以先安装我们的 pre-commit 钩子来检查风格违规:
$ pip install pre-commit
$ pre-commit install --install-hooks
然后运行:
$ make ruff # alias for `pre-commit run -a`
在 shell 中执行。一些文本编辑器或 IDE 也提供 Python 风格检查,可以在编码时高亮风格错误(并让你逐渐减少这类错误)。Spyder IDE 内置了该功能,大多数编辑器也有提供类似功能的插件。搜索 python linter <name of your favorite editor> 了解更多。
使用一致的变量命名#
类应使用 CamelCase 命名。函数和实例/变量应使用 snake_case(用 n_samples 而不是 nsamples)。避免使用单字符变量名,除非在推导式(comprehension)或生成器(generator)内部。
文档字符串(docstring)主要遵循 NumPy 风格#
大多数情况下,你可以查看现有的 MNE-Python 文档字符串来了解自己的应如何格式化。如果找不到相关示例,请查阅 NumPy 文档字符串风格指南,了解更复杂格式(如嵌入示例代码、引用文献或包含渲染的数学公式)的示例。请注意,我们在几个方面与 NumPy 文档字符串标准有所不同:
我们使用名为
sphinxcontrib-bibtex的模块来渲染引用。搜索我们的源代码(git grep footcite和git grep footbibliography)可查看如何向文档字符串、示例或教程添加正文引用和格式化参考文献的示例。结构化文献数据位于doc/references.bib;添加新参考文献时请遵循现有的键名方案(例如Singleauthor2019、AuthoroneAuthortwo2020、FirstauthorEtAl2021a、FirstauthorEtAl2021b)。对于可选关键字参数,我们不明确写“optional”(因为从函数或方法签名就能看出哪些参数有默认值)。
对于可能接受多种类型的参数,我们使用竖线而不是“or”一词,例如:
param_name : str | None。我们不包含描述可能出现的错误/警告的 Raises 或 Warns 小节。
对于简单的私有函数/方法,文档字符串可以简短;但当私有函数/方法相对复杂时,应提供完整的文档字符串。要对文档运行一些基本测试,可以使用:
$ pytest mne/tests/test_docstring_parameters.py
$ make ruff
处处使用交叉引用#
文档字符串和专门的文档页面(教程、操作示例、讨论和术语表)都应包含对所提及的任何模块、类、函数、方法、属性或文档页面的交叉引用。这些都有对应的 Sphinx 角色(:mod:、:class:、:func:、:meth:、:attr:、:doc:),此外还有用于链接到文档页面特定小节的通用交叉引用指令(:ref:)。
警告
有些 API 元素有多个暴露位置(例如 mne.set_config 和 mne.utils.set_config)。交叉引用要生效,必须与 doc/python_reference.rst 中的条目匹配(因此 :func:`mne.set_config` 有效,而 :func:`mne.utils.set_config` 无效)。
MNE-Python 还使用 Intersphinx,因此你可以(也应该)交叉引用 Python 内置类和函数,以及 NumPy、SciPy 等中的 API 元素。所链接的 Intersphinx 项目列表请参阅 Sphinx 配置文件(doc/conf.py)。可以使用 sphobjinv 等工具检查它们的清单,或用如下命令将其转储到文件:
$ python -m sphinx.ext.intersphinx https://docs.python.org/3/objects.inv > python.txt
注意,任何由单反引号包围、且前面没有 API 角色(:class:、:func: 等)的内容,都会被假定位于 MNE-Python 命名空间中。这可以省去一些输入,尤其在教程中;例如不必写 see :func:`mne.io.Raw.plot_psd` for details,而可以直接写 see `mne.io.Raw.plot_psd` for details。
其他风格指导#
代码组织#
导入#
按以下顺序导入模块,最好在每个子节内按字母排序:
Python 内置模块(
copy、functools、os等)NumPy(
import numpy as np)以及在测试文件中的 pytest(import pytest)MNE-Python 内部导入(例如
from .pick import pick_types)
从 MNE-Python 的其他部分导入时,主代码库使用相对导入,测试、教程和操作示例使用绝对导入。对 matplotlib、scipy 以及可选模块(sklearn、pandas 等)的导入应嵌套进行(即放在函数或方法内部,而不是文件顶部)。这有助于缩短导入时间并减少使用 MNE 的硬性依赖。
返回类型#
方法应原地修改并返回 self,函数应返回副本(在适用的情况下)。文档字符串应始终为返回值给出有意义的名称,即使函数或方法的返回值在代码中从未以该名称保存。
可视化#
可视化功能应同时以函数和方法两种形式提供。将公共可视化函数添加到 mne.viz 子模块,并从相应的对象方法中调用这些函数。例如,方法 mne.Epochs.plot() 内部调用函数 mne.viz.plot_epochs()。
所有可视化函数都必须接受布尔型 show 参数,通常返回 matplotlib.figure.Figure(或 Figure 对象列表)。3D 可视化函数返回 mne.viz.Figure3D、mne.viz.Brain 或其他合适的返回类型。
对于中间值(零点)有意义的带符号数据,可视化函数应默认使用 RdBu_r 颜色映射,其他情况使用 Reds。这同样适用于可视化函数和教程/示例。
运行测试套件#
在 mne-python 根目录下执行 pytest -m "not ultraslowtest" mne 可以运行完整测试套件。不过测试整个模块可能相当慢,因此在开发新功能时若要运行单个测试,可以执行以下命令:
$ pytest mne/tests/test_evoked.py::test_io_evoked --verbose
或者:
$ pytest mne/tests/test_evoked.py -k test_io_evoked --verbose
请确保已拥有 testing 数据集,可在 Python 解释器中运行以下代码获取:
>>> mne.datasets.testing.data_path(verbose=True)
构建文档#
我们的文档(包括代码文件中的文档字符串)采用 reStructuredText 格式,并使用 Sphinx 和 Sphinx-Gallery 构建。确保文档贡献格式正确的最简单方法是:遵循本页的风格指南、模仿现有文档示例、在不确定如何格式化时参考 Sphinx 和 Sphinx-Gallery 参考材料,并在提交拉取请求前于本地构建文档以确认一切正常。
你可以使用 GNU Make 和 doc/Makefile 在本地构建文档。在 doc 目录内,可以运行以下命令测试格式和链接:
$ make html-noplot
这将构建文档,但只格式化(而不执行)教程和示例文件。如果你创建或修改了示例或教程,则应改为运行 make html-pattern PATTERN=<REGEX_TO_SELECT_MY_TUTORIAL>,以渲染所有文档,并额外只执行你的示例或教程(从而确认它能成功运行并生成预期的输出/图)。
上述任一命令完成后,make show 会在浏览器中打开本地渲染的文档站点。如果你看到许多似乎与你的贡献无关的警告,可能是文档构建的输出文件夹中包含过时、已无关的文件。运行 make clean 可以清理它们。还有更多 make 配方可用;可在 doc 目录运行 make help,或查阅 Sphinx-Gallery 文档了解详情。
修改命令行工具#
MNE-Python 通过 Python 为有限的一组命令行工具提供支持。这些工具通常这样调用:
$ mne browse_raw ~/mne_data/MNE-sample-data/MEG/sample/sample_audvis_raw.fif
它们通常是为了方便而提供,可用于快速调试(此处用于 mne.io.Raw.plot)。
如果某个命令行函数失败,也可以通过 python -m 作为 mne 模块的一部分执行。例如:
$ python -i -m mne browse_raw ...
由于使用了 python -i 启动,脚本结束后会进入 Python 终端。这在出现错误时很有用,因为随后可以进入事后调试器(post-mortem debugger):
>>> import pdb; pdb.pm()
GitHub 工作流程#
MNE-Python 贡献者和维护者社区中的几乎每个人都是在职的科学家、工程师或学生,利用业余时间为 MNE-Python 做贡献。因此,我们采用了一套最佳实践来简化协作和审查流程。这些实践大多在许多开源软件项目中都很常见,因此在参与 MNE-Python 时学会遵循它们,对你日后为其他项目做贡献也会大有裨益。以下是指南:
搜索 GitHub issues 页面(包括 open 和 closed 的 issue),以防他人已开始处理同一个 bug 修复或功能。如果没有找到,请先开一个新 issue 与维护者讨论,然后再着手你提议的修改。
每个拉取请求(PR)只实现一个新功能或一个 bug 修复。偶尔一次修复几个相关 bug 可能合理,但这会让 PR 更难审查和测试,因此这样做之前请先与 MNE-Python 维护者确认。避免对代码做纯粹表面性的修改;它们会让 PR 更难审查。
通常最好从
main以外的分支创建 PR,这样在需要时可以方便地用main分支回到可正常工作的代码状态(例如,当你同时进行多项修改,或需要拉取他人最新修改才能让新功能正常工作时)。大多数情况下,应将 PR 提交到上游的
main分支,除非维护者明确要求你向其他分支提交 PR(例如,针对当前稳定版的回溯移植或维护性 bug 修复)。别忘了在 PR 中通过变更日志条目包含对修改的简短说明(相关说明见上文的变更日志一节)。
我们的社区使用以下提交标签和约定:
进行中的 PR 应创建为草稿(draft)PR,且 PR 标题应以
WIP开头。当你认为 PR 已准备好接受审查和合并时,请将其从草稿 PR 转换为普通 PR,把标题改为以
MRG开头,并在 PR 中添加评论请求审查(仅修改标题不会自动通知维护者)。仅影响文档的 PR 还应标记
DOC,bug 修复应标记FIX,新功能应标记ENH(即“enhancement”)。STY用于风格变更(即在不改变内容的前提下改进文档字符串的一致性或格式)。以下提交标签用于与我们的持续集成(CI)提供方交互。请谨慎使用;不要仅仅因为测试失败就跳过测试:
[skip circle]跳过 CircleCI,它用于测试我们的文档能否成功构建。[skip actions]跳过我们的 GitHub Actions,它用于测试在 Linux 和 macOS 系统上的安装与执行。[skip azp]跳过 Azure,它用于测试在 Windows 系统上的安装与执行。[ci skip]是[skip actions][skip azp][skip circle]的别名。注意[skip ci]不是有效标签。[circle full]会触发“完整”文档构建,即执行教程和操作示例中的所有代码(而不仅是良好地格式化),并将生成的输出和图渲染为教程/示例的一部分。
示例和教程应在传达必要信息的同时尽可能快速执行并尽量降低内存占用。要查看当前执行时间和内存使用情况,请访问
sg_execution_times页面。要查看未使用的 API 条目,请参阅sg_api_usage页面。
这个示例拉取请求体现了上面列出的许多约定:它只解决一个问题;它从讨论该问题及一些可能解决方案的 issue 开始;它是一个从用户的非 main 分支提交到上游 main 分支的 PR;它将不同类型的修改分开提交,并使用 DOC、FIX 和 STY 等标签,便于维护者审查变更集;等等。如果你是 GitHub 新手,它可以作为了解 PR 审查流程的有用示例。