SymPy 1.13 中文文档(五十八)
贡献
贡献指南详细介绍了如何为 SymPy 做贡献。
如果您是 SymPy 的新贡献者,请从贡献简介开始。
内容
-
贡献简介
-
新贡献者指南
-
设置开发环境
-
开发工作流程
-
编写测试
-
构建文档
-
-
依赖项
-
调试
-
文档字符串风格指南
-
文档风格指南
-
弃用政策
入门指南
原始文档:
docs.sympy.org/latest/contributing/introduction-to-contributing.html
SymPy 是由大量贡献者创建和维护的,我们希望您也能成为其中之一!对于新贡献者来说,加入 SymPy 这样一个大型而复杂的机器可能有些令人畏惧。本页面旨在为新贡献者提供入门提示。
熟悉软件使用
我们建议您先浏览 SymPy 教程 以熟悉使用本软件,然后再开始贡献。
这个教程也有视频可供参考:
阅读论文
我们在 2017 年撰写了一篇期刊论文,概述了 SymPy 及其功能。您可以在这里阅读:
浏览文档
除了教程之外,文档 中还包含大量信息。浏览不同主题可能是个好主意,以了解其他可用内容。
查看行为准则
SymPy 社区的参与者需遵守我们的 行为准则。在开始之前,请先查阅此文档。
加入我们的邮件列表
SymPy 邮件列表 是讨论 SymPy 的地方之一。您可以在这里提问如何使用 SymPy,讨论功能请求,讨论软件漏洞,或分享您如何使用 SymPy。请在 Google Groups 页面上请求加入列表。请注意,为了防止垃圾邮件,您第一次发帖的信息将需要经过审核才能发布到列表上。在发帖前,请阅读 shakthimaan.com/downloads/book/chapter1.pdf 以熟悉邮件列表礼仪。
设置您的开发环境
我们使用 Git 版本控制系统跟踪软件的 时间变化,并有效地管理来自多个作者的 贡献。我们还广泛使用 GitHub 作为 Git 的 Web 接口,用于通信、问题跟踪、合并贡献(即拉取请求)等。
如果您对 Git 和 GitHub 不熟悉,请首先阅读 设置开发环境 页面,获取有关如何设置开发环境的说明。如果您已经熟悉基本的 GitHub 工作流程,请阅读 开发工作流程 页面,了解与 SymPy 特定的 GitHub 贡献工作流程相关的方面。
辨识需要处理的内容
有很多方法可以为 SymPy 做出贡献。大多数贡献集中在修复软件 bug 和为他们感兴趣的新功能添加功能。但我们还需要帮助维护我们的网站、编写文档、准备教程、回答邮件列表、聊天室、StackOverflow 和问题跟踪器上的人们的问题,以及审查拉取请求。以下是一些开始贡献的方式:
SymPy 代码库
开始使用主代码库的最佳方法是修复一些现有的 bug。如果您正在寻找要修复的 bug,可以查看问题跟踪器中标记为 “Easy to fix” 的问题,看看是否有您感兴趣的。如果不清楚如何修复它,请在问题本身或邮件列表上寻求建议。
SymPy 的代码被组织成 Python 的包和模块。核心代码位于 sympy/core 目录中,而 sympy 目录中的其他包包含更具体的代码,例如 sympy/printing 处理 SymPy 对象在终端和 Jupyter 中的打印方式。
文档
SymPy 的文档分布在两个地方:
-
源代码中函数的文档字符串:
github.com/sympy/sympy/tree/master/sympy
这两者最终显示在此文档网站上。您可以点击任何函数文档旁边的“[Source]”链接,转到对应的 SymPy 源代码中的文档字符串。
- SymPy 中的每个函数和类都有一个在调用签名下面的字符串,解释对象的用途。当您在 Python 中键入
help(function_name)时,就会显示这个内容。
在为我们的文档做贡献或改进时,请遵循 SymPy 文档风格指南。
审查拉取请求
每个对 SymPy 的贡献都需要通过一个拉取请求 github.com/sympy/sympy/pulls。我们要求每个拉取请求在合并之前都要经过审查。如果你对 SymPy 代码库的某个部分和 SymPy 的开发流程有所了解,审查他人的拉取请求对社区是有帮助的。你可以查看代码提交并检查其是否实现了预期功能。
新贡献者指南
原文链接:
docs.sympy.org/latest/contributing/new-contributors-guide/index.html
本指南介绍了如何为新贡献者向 SymPy 贡献代码。
本指南根据你在开源方面的经验水平分为几个部分。
如果你以前从未为开源项目做过贡献,请从设置开发环境开始。
如果你已经熟悉如何使用 git 和 GitHub 的基础知识,但以前从未为 SymPy 贡献过,请从开发工作流程开始。
索引
-
设置开发环境
-
开发工作流程
-
撰写测试
-
构建文档
设置开发环境
原文链接:
docs.sympy.org/latest/contributing/new-contributors-guide/dev-setup.html
本指南适用于以前从未在 GitHub 上为开源项目做出过贡献的人。 如果你已经完成了本指南中的步骤,则无需再次完成。
注意
本指南适用于以前从未在 GitHub 上为开源项目做出过贡献的人。 如果你已经熟悉如何在 GitHub 上为开源项目做出贡献,请参阅开发工作流程过程指南。
向代码库贡献的第一步是创建你的开发环境。
重要信息
本指南中的每一步只需要执行一次。 一旦完成,即使是进行第二次贡献,也不需要重复执行。
安装 Git
SymPy 可以在GitHub上找到,并使用Git进行源代码控制。 工作流程是通过主存储库拉取和推送代码。 为你的操作系统安装相应版本的 Git 以开始开发。
类似 Linux 的系统:
通过你的本地包管理系统安装 git:
yum install git
或:
sudo apt-get install git
Windows 和 macOS:
获取 git 的最简单方法是下载GitHub 桌面版,这将安装 git,并提供一个漂亮的图形界面(本教程将基于命令行界面)。 请注意,你可能需要进入 GitHub 首选项,并选择“安装命令行工具”选项以将 git 安装到终端中。
如果你决定使用 GitHub 图形界面,请确保在设置中禁用任何“同步进行变基”的选项。
配置你的 Git 中的姓名和电子邮件
Git 通过检查用户的姓名和电子邮件来跟踪谁提交了每个提交。 此外,我们使用此信息将你的提交与你的 GitHub 账户关联起来。
要设置这些内容,请输入下面的代码,用你自己的名称和电子邮件替换(--global是可选的):
git config --global user.name "Firstname Lastname"
git config --global user.email "your_email@youremail.com"
名称应为你的实际名称,而不是你的 GitHub 用户名。 使用你在 GitHub 账户中使用的电子邮件(见下文的#dev-setup-create-github-account)。
(可选)配置 Git 设置
这一步骤并非必需,但可以使你在命令行上使用 git 更容易。
这些全局选项(即适用于所有存储库)位于~/.gitconfig中。 如果你愿意,你可以编辑此文件以启用一些便捷的快捷方式:
[user]
name = Firstname Lastname
email = your_email@youremail.com
# Some helpful aliases to save on typing
[alias]
ci = commit
di = diff --color-words
st = status
co = checkout
log1 = log --pretty=oneline --abbrev-commit
logs = log --stat
查看git-scm.com/book/sv/v2/Customizing-Git-Git-Configuration获取一些常见的 git 配置选项。
设置 GitHub
接下来,您需要设置您的 GitHub 帐户。请注意,这里的所有步骤只需执行一次。如果您已经有一个 GitHub 帐户并设置了 SSH 密钥,即使它是为 SymPy 以外的其他项目,也无需再次执行。
创建 GitHub 帐户
要贡献给 SymPy,需要一个 GitHub 帐户。如果您还没有,请在github.com/join注册。您的 GitHub 帐户是您在开源世界中的存在,因此我们建议选择一个专业的用户名。
设置 SSH 密钥
要在您的计算机和 GitHub 之间建立安全连接,请参阅详细说明docs.github.com/get-started/getting-started-with-git/set-up-git,或者在docs.github.com/authentication/connecting-to-github-with-ssh/adding-a-new-ssh-key-to-your-github-account查看。
如果您在访问 GitHub 的 SSH 方面遇到任何问题,请阅读docs.github.com/authentication/troubleshooting-ssh的故障排除说明,或者在邮件列表上向我们询问。
分支 SymPy
创建您自己的分支SymPy 项目在 GitHub 上。如果您之前已经这样做过,则不需要再次进行。
转到SymPy GitHub 仓库,然后单击Fork按钮。
现在您已经拥有了自己的 SymPy 项目的仓库。分支项目的地址将看起来类似于https://github.com/<your-github-username>/sympy,其中<your-github-username>是您的 GitHub 用户名。
获取 SymPy 代码
建议为开发目的创建 SymPy 项目的分支。创建 SymPy 项目的您自己的分支(如果尚未)。前往 SymPy GitHub 仓库:
https://github.com/sympy/sympy
现在您将在https://github.com/<your-user-name>/sympy拥有一个分支。
然后,在您的计算机上浏览到您希望存储 SymPy 的位置,并从 SymPy 的原始仓库克隆(下载)最新代码(约 77 MiB):
$ git clone https://github.com/sympy/sympy
然后将您的读写库分配给一个名为“github”的远程仓库(将<your-github-username>替换为您的 GitHub 用户名):
git remote add github git@github.com:<your-github-username>/sympy.git
要了解更多关于 GitHub 分叉和调优的信息,请参见:docs.github.com/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests,docs.github.com/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo,以及docs.github.com/get-started/quickstart/set-up-git。
配置完成后,您的设置应该类似于这样:
$ git remote -v
origin https://github.com/sympy/sympy (fetch)
origin https://github.com/sympy/sympy (push)
github https://github.com/<your-github-username>/sympy (fetch)
github https://github.com/<your-github-username>/sympy (push)
虚拟环境设置
您可能希望利用虚拟环境来隔离您的 SymPy 开发版本,以避免受到系统范围内安装的版本的影响,例如来自apt-get install python-sympy。
如果您使用conda,您可以使用它来创建虚拟环境:
$ conda create -n sympy-dev -c conda-forge --file requirements-dev.txt
如果您喜欢使用pip和venv,您可以使用类似以下的内容
cd sympy
python -m venv .venv
source .venv/bin/activate
pip install -r requirements-dev.txt
您可以在此命令中添加任何其他您可能发现对您的贡献有用的包,例如可选依赖项。
现在您已经有了一个可以用来测试您的 SymPy 开发副本的环境。
现在激活环境:
$ conda activate sympy-dev
开发工作流程
原文链接:
docs.sympy.org/latest/contributing/new-contributors-guide/workflow-process.html
注意
本指南适用于那些已经熟悉在 GitHub 上为开源项目做贡献的人士。如果你是 GitHub 的新手,请先阅读设置开发环境指南。
贡献清单
这是提交到 SymPy 的拉取请求需要完成的事项清单。这些事项在合并拉取请求之前都必须完成。在打开拉取请求之前,不必全部完成这些事项,但通常在打开拉取请求之前或提交更改之前先检查基本事项是个好主意。
-
确保代码质量检查通过。
./bin/test quality flake8 sympy/ -
添加测试。 所有新功能应进行测试。Bug 修复应添加回归测试。测试采用 pytest 的
assert f(x) == y风格,并包含在sympy/源代码中相应的tests目录中。有关编写测试的指南请参见该指南。 -
新的公共函数和方法应有文档字符串。
-
文档字符串应包含 doctests。
-
确保所有测试通过。 在提交之前,您可能需要在本地运行相关的测试套件(例如,
./bin/test solvers)。当您打开一个拉取请求时,所有测试将在 CI 上运行。在合并 PR 之前,CI 必须全部通过。 -
编写良好的提交消息。
-
(首次贡献者专用)将您的名字添加到
.mailmap文件中。如果未正确完成此操作,则 GitHub 上的“test/authors”CI 构建将失败。 -
在拉取请求描述中交叉引用相关问题。 如果拉取请求修复了问题(即该问题应在 PR 合并后关闭),请使用“fixes #123”语法。
-
为了可见性,在原问题中添加评论,跨引用拉取请求。如果没有相应的问题,这也可以。除非您的 PR 需要进一步改进,否则无需打开问题。
-
添加发布说明条目。应在打开拉取请求时完成,在拉取请求描述字段中。在拉取请求合并之前可以随时编辑。
-
回应审查评论。 所有 SymPy 拉取请求必须在合并之前由其他人审查。
选择要修复的问题
要开始主代码库的最佳方法是修复一些现有的 bug。查看问题跟踪器中的“易于修复”问题,看看是否有您感兴趣的问题。如果您想尝试修复它,请在问题中创建一条消息表明您想要处理它。如果不清楚如何修复,请在问题本身或邮件列表上寻求建议。
SymPy 的代码组织成 Python 包和模块。核心代码位于sympy/core目录中,sympy 目录中的其他包含更具体的代码。例如,sympy/printing包含处理如何将 SymPy 对象打印到终端和 Jupyter 的代码。
如果要进行的更改还没有问题,那么在开始之前没有必要先打开问题。只有在您觉得需要在提交拉取请求之前讨论更改时才需要这样做,例如,如果您不确定某事实际上是否是一个 bug,或者如果您不确定新功能是否在范围内。如果有变更,只需直接打开拉取请求并在那里讨论即可。有了实际代码后,讨论会更容易进行,因此如果您有更改,即使这些更改尚未完全准备好合并,也最好打开拉取请求。
创建一个新的分支
在修改代码之前要做的第一件事是在 git 中创建一个分支。
记住,永远不要提交到master分支。master只应用于从主要的 sympy/sympy 存储库拉取上游更改。如果您提交到master,将很难拉取这些更改,并且如果您希望一次提交多个拉取请求,也会很困难。
首先选择一个分支名称。参见下面的分支名称。要创建和检出(即使其成为工作分支)新分支,请运行
# Pull any upstream changes from the main SymPy repo first
git checkout master
git pull
git branch <your-branch-name>
git checkout <your-branch-name>
最后两个命令也可以合并成一个单独的命令:
git checkout -b <your-branch-name>
要查看所有分支,并突出显示当前分支,请键入:
git branch
而且记住,永远不要在主分支输入以下命令:git merge,git add,git commit,git rebase。如果您不小心向本地主分支提交了一些提交,您将不得不硬重置以删除这些提交。
分支名称
使用一个短小且易于输入的分支名称,与所做的更改有关联。记住,希望尝试您的代码的开发人员将需要在命令行中输入您的分支名称。
避免在分支名称中使用问题编号(大多数 SymPy 问题编号为 5 位数),因为这些不易于输入,并且不会在没有查看问题的情况下明确表明更改的内容。
一些好的分支名称示例包括
fix-solve-bug
typo-fix
core-improvements
add-simplify-tests
最终,分支名称并不是非常重要,所以不要花太多时间考虑它。它的唯一功能是将此贡献的代码与您可能进行的其他贡献区分开来。
修改代码
在修复问题时,请记住每个贡献都应该遵循几个要求:
代码质量
SymPy 的贡献必须具备足够的代码质量才能被接受。有一些代码质量检查将在您创建拉取请求后自动在 CI 上运行,但您也可以在本地运行它们
./bin/test quality
flake8 sympy/
此外,所有的测试都是必须通过的。CI 将自动运行测试,但您也可以自行运行它们(请参阅#workflow-process-run-tests)。建议在提交之前至少运行与您修改的代码相关的测试,以确保您没有犯任何错误或意外地破坏了某些东西。
一旦提交拉取请求后,请在 GitHub Actions 检查完成后查看是否有任何测试失败。如果有失败的测试,您需要在拉取请求被接受之前修复它们。 ### 添加测试
所有新功能都应该经过测试。如果您正在修复错误,则应附带回归测试。即,在修复错误之前会失败的测试,但现在会通过。通常可以使用来自问题的代码示例作为测试用例,尽管简化此类示例或编写自己的示例同样可以,只要它测试了相关问题。
测试位于与代码相邻的tests/目录中,文件名为test_<thing>.py。在大多数情况下,如果您修改了sympy/<submodule>/<file>.py,那么该功能的测试将放在sympy/<submodule>/tests/test_<file>.py中。例如,sympy/simplify/sqrtdenest.py中函数的测试位于sympy/simplify/tests/test_sqrtdenest.py中。对于此规则,有一些例外,因此通常尝试找到函数的现有测试位置,并将您的测试添加到它们旁边。
测试遵循一个简单的模式,通过阅读现有的测试文件应该是显而易见的。测试以test_开头的函数形式存在,并包含类似以下内容的行
assert function(arguments) == result
例如
# from sympy/functions/elementary/tests/test_trigonometric.py
def test_cos_series():
assert cos(x).series(x, 0, 9) == \
1 - x**2/2 + x**4/24 - x**6/720 + x**8/40320 + O(x**9)
如果相关,可以将新的测试用例添加到现有的测试函数中,或者可以创建一个新的测试函数。
文档
所有新的方法、函数和类都应该有一个文档字符串来展示如何使用它们。文档字符串是一个三引号字符串,紧跟在描述函数的def行后面。文档字符串应该遵循文档字符串风格指南中概述的格式。
每个文档字符串中应该包含的一个重要内容是示例。示例也被称为 doctests,因为它们通过 bin/doctest 脚本来测试以确保输出是正确的。
Doctests 需要包括每个使用的函数导入定义任何使用的符号。用户应该能够复制和粘贴示例输入到他们自己的 Python 会话中,并获得完全相同的输出。from sympy import * 不允许在 doctests 中使用,因为这会使得从 SymPy 中来的函数不清晰。
文档字符串样式指南详细介绍了如何在文档字符串中格式化示例。
请记住,doctest 并不是测试。可以将它们视为被测试的示例。一些关键区别如下:
-
编写 doctest 以提供信息;编写常规测试以检查回归和边界情况。
-
doctest 可以随时更改;常规测试不应更改。
特别地,如果修改或删除 doctest 能使文档字符串更容易理解,我们应该能够随时这样做。
这是一个带有 doctest 的示例文档字符串(来自sympy/functions/special/delta_functions.py)。
def fdiff(self, argindex=1):
"""
Returns the first derivative of a Heaviside Function.
Examples
========
>>> from sympy import Heaviside, diff
>>> from sympy.abc import x
>>> Heaviside(x).fdiff()
DiracDelta(x)
>>> Heaviside(x**2 - 1).fdiff()
DiracDelta(x**2 - 1)
>>> diff(Heaviside(x)).fdiff()
DiracDelta(x, 1)
"""
if argindex == 1:
return DiracDelta(self.args[0])
else:
raise ArgumentIndexError(self, argindex)
另外,所有公共函数的文档字符串应包含在 Sphinx API 文档中。根据模块的不同,这可能意味着您需要在相应的doc/src/modules/<module>.rst文件中添加一个.. autofunction::行。您应该生成文档,并查看渲染后的 HTML 以确保没有标记错误。
如果你想写一份更详尽的指南或教程,可以将其包含在 Sphinx 文档中,格式为 Markdown 或 RST 文件,而不是放在文档字符串中。虽然这对新贡献并不是必需的,但我们始终欢迎添加新的写作精良的长篇指南到我们的文档中。
一旦您在 GitHub 上发起了拉取请求,CI 将自动构建预览文档,您可以查看。在拉取请求页面,滚动到底部,找到显示“点击这里查看文档预览”的链接。
运行测试
有几种运行 SymPy 测试的方法,但最简单的方法是使用bin/test脚本。
该脚本接受多个选项和参数。运行bin/test --help以获取所有支持的参数。在幕后,它使用pytest,如果您喜欢,也可以直接使用它。
使用以下命令运行所有测试:
$ ./bin/test
要运行特定文件的测试,请使用:
$ ./bin/test test_basic
其中test_basic来自文件sympy/core/basic.py。
要运行模块的测试,请使用:
$ ./bin/test /core /utilities
这将运行core和utilities模块的测试。
同样地,运行质量测试:
$ ./bin/test code_quality
提交更改
一旦更改准备就绪,您应该提交它们。您可以检查哪些文件已更改:
git status
检查总体变更:
git diff
如果你创建了任何新文件,请使用以下方式添加它们:
git add new_file.py
你已经准备好在本地提交更改。提交还包括描述其内容的commit message。有关撰写良好提交消息的指南,请参阅下一节。输入:
git commit
在这种情况下,将自动弹出编辑器窗口。在 Linux 中,默认情况下是 vim。你可以通过更改$EDITOR shell 变量来改变弹出的编辑器。
同样,通过选项-a的帮助,您可以告诉commit命令自动暂存已修改和删除的文件,但您未告知 git 的新文件将不受影响,例如:
git commit -a
如果你想只暂存部分更改,可以使用交互式提交功能。只需键入:
git commit --interactive
并选择你希望在结果界面中看到的更改。
删除垃圾文件
很多编辑器可能会在你的 SymPy 目录下创建一些配置文件、二进制文件或临时文件,在合并提交前应该将它们删除。
追踪单个文件可能很麻烦。
你可能会考虑使用 .gitignore,不过编辑 .gitignore 本身应该得到社区的同意。
使用 .git/info/exclude 是最好的选择,因为它只在本地应用。
docs.github.com/get-started/getting-started-with-git/ignoring-files
编写提交消息
提交消息有两部分:标题(第一行)和正文。两者之间用空行分隔。
提交消息总结了提交的操作。与代码一样,你的提交消息将成为项目 git 历史的永久部分。因此,你应该在确保高质量的基础上付出一些努力。提交消息是为人类读者准备的,既包括当前正在审查你代码的人,也包括未来在研究代码变更时可能遇到你的提交的人。因此,在这里包含有助于其他人理解你的提交的信息,如果有必要的话。
像 git shortlog 和 GitHub UI 默认只显示提交的第一行,因此在第一行传达提交的最重要方面是很重要的。
-
第一行保持 71 个字符或更少,后续行保持 78 个字符或更少。这样可以使日志的单行形式显示摘要而不换行。
-
确保在摘要后留一行空白
-
不要在第一行结束时使用句点(句号)。后续行应该使用句号。
-
如果可能的话,为提交提供上下文信息,
例如
integrals: Improved speed of heurisch()而不是只有Improved speed of heurisch() -
引用任何相关的问题编号。你不需要为更改本身引用拉取请求,但应该引用修复的问题,可以用
#12345或https://github.com/sympy/sympy/issues/12345。你还应该提供一个问题的简要摘要,而不仅仅是引用问题编号,这样别人就不必四处寻找上下文。 -
提交不一定总是在你的分支上下文中看到,因此为每个提交提供一些上下文通常是有帮助的。虽然不是必需的,因为查看提交元数据以查看修改文件或查看附近相关提交的提交历史并不难。
-
使用简洁明了的英语。使用完整的句子。
-
描述实际发生了什么变化。不要只写像
Modified solvers.py这样的简短提交消息。人们已经可以从提交的差异中看到修改了哪些文件。消息的目的是告诉他们差异实际上做了什么,这样他们就不必试图弄清楚。同样地,虽然应如上所述交叉引用相关问题,但消息应包含足够基本的摘要,以便人们可以理解正在发生什么,而无需查阅问题。对于感兴趣的人,问题可以提供更详细的背景信息。 -
尽量避免使用像“Fix”这样的简短提交消息,以及不提供上下文的提交消息,比如“找到了 bug”。如果不确定,较长的提交消息可能比较好。避免使用
-m开关来git commit在命令行上编写提交消息。相反,让它打开您的编辑器,以便您可以写一个更长的提交消息。 -
如果仅仅通过查看差异无法弄清楚,那么请提供提交的概述。
-
包括其他相关信息,例如
-
已知问题
-
一个具体的例子(用于添加新功能/改进性能等的提交)
-
-
当适合时,请使用项目符号列表。
-
随意使用 Unicode 字符,例如来自 SymPy Unicode 漂亮打印机的输出。
良好提交消息的示例
这是来自提交 bf0e81e12a2f75711c30f0788daf4e58f72b2a41 的提交消息示例,这是 SymPy 历史的一部分:
integrals: Improved speed of heurisch() and revised tests
Improved speed of anti-derivative candidate expansion and solution
phases using explicit domains and solve_lin_sys(). The upside of
this change is that large integrals (those that generate lots of
monomials) are now computed *much* faster. The downside is that
integrals involving Derivative() don't work anymore. I'm not sure
if they really used to work properly or it was just a coincidence
and/or bad implementation. This needs further investigation.
Example:
In [1]: from sympy.integrals.heurisch import heurisch
In [2]: f = (1 + x + x*exp(x))*(x + log(x) + exp(x) - 1)/(x + log(x) + exp(x))**2/x
In [3]: %time ratsimp(heurisch(f, x))
CPU times: user 7.27 s, sys: 0.04 s, total: 7.31 s
Wall time: 7.32 s
Out[3]:
⎛ 2 x 2⋅x x 2 ⎞
log⎝x + 2⋅x⋅ℯ + 2⋅x⋅log(x) + ℯ + 2⋅ℯ ⋅log(x) + log (x)⎠ 1
──────────────────────────────────────────────────────────── + ───────────────
2 x
x + ℯ + log(x)
Previously it took 450 seconds and 4 GB of RAM to compute.
共同作者
偶尔会有多人作为团队为一个 PR 工作,或者您已经应用了社区的一些建议。
对于这些情况,您可以使用 GitHub 的共同作者功能,通过添加
Co-authored-by: NAME NAME@EXAMPLE.COM
Co-authored-by: AUTHOR-NAME ANOTHER-NAME@EXAMPLE.COM
创建一个拉取请求
一旦您的更改准备好进行审查,请将它们推送到 GitHub 并提交拉取请求。
在完全准备好更改之前提交拉取请求也是可以的,以获取一些早期反馈。在投入过多时间之前,尽早获取反馈是更好的。如果您的拉取请求尚未完全准备好合并,请在 GitHub 上将其设置为“草稿”状态。您还可以在拉取请求标题的开头添加“[WIP]”(表示“工作正在进行中”)来指示这一点。只需确保在您的 PR 准备好进行最终审查时删除“草稿”状态或 [WIP]。
撰写拉取请求的标题和描述
当您提交拉取请求时,请确保填写拉取请求描述模板。这包括添加对任何相关问题的交叉引用(如适用的话加上“修复”),以及添加发布说明条目。
-
描述性标题非常重要。 拉取请求标题应指示修复了什么问题。具有不描述性标题的拉取请求通常会被审阅者忽略。
不好的拉取请求标题示例有
-
“修改了 solvers.py”
-
“修复问题 #12345”
这些确实向审阅者指示了实际更改的内容,因此他们可能会仅仅浏览而不进行审查。更好的拉取请求标题示例包括
- “修复了在超越函数上的 solve() 函数中的一个 bug”
-
-
在拉取请求标题中不要包含问题编号或文件名。问题编号应该放在描述中。
-
如果您没有准备好合并拉取请求,请使用 DRAFT 状态或在标题中包含 “[WIP]” 前缀,并在准备就绪后移除状态/前缀。
描述部分是:
-
展示您的工作成果,可能会比较主分支的输出与您的更改后的输出
-
参考已解决的问题,例如“#1234”;该格式将自动创建到相应问题或拉取请求的链接,例如“这类似于问题 #1234 中的问题…”。此格式在拉取请求的讨论部分也适用。
-
使用类似“closes #1234”或“fixed #1234”(或类似的 自动关闭语法)的短语。然后,当您的拉取请求合并时,那些其他问题或拉取请求将自动关闭。注意:此语法在拉取请求的讨论中不起作用。请参阅此 快速指南 了解从拉取请求中自动关闭问题的有效和无效语法。
-
拉取请求需要一个发布说明条目。请参阅编写发布说明以了解如何在拉取请求描述中编写发布说明。SymPy Bot 将自动检查您的 PR 是否有发布说明。
最好只填写拉取请求模板(在打开拉取请求时显示的文本)。如果您填写了模板中的所有部分,那么您将拥有一个很好的拉取请求描述。
将您的姓名和电子邮件地址添加到 .mailmap 文件中。
每位作者的姓名和电子邮件地址都存储在AUTHORS文件中,但不应直接编辑此文件。当基于提交记录的姓名和电子邮件地址发布 SymPy 的新版本时,AUTHORS 文件将自动更新。使用 git 进行的每个提交都存储有 git 配置的名称和电子邮件地址(请参阅配置 git 设置)。.mailmap文件用于将提交记录中记录的名称/电子邮件与将列在 AUTHORS 文件中的作者姓名和电子邮件地址关联起来。
当您首次提交拉取请求时,您需要通过添加一行如下的方式将您的姓名和电子邮件地址添加到.mailmap 文件中:
Joe Bloggs <joe@bloggs.com> joeb <joe@bloggs.com>
.mailmap 文件中的这一行将作者姓名与相应的提交关联起来。第一个名称和电子邮件地址最终将显示在 AUTHORS 文件中。第二个条目是在提交元数据中记录的内容(请参阅将用户名映射到 AUTHORS 文件条目)。
提交的元数据名称和电子邮件应与进行提交之前通过 git 配置的名称和电子邮件完全匹配(请参阅配置 git 设置)。bin/mailmap_check.py脚本可以检查是否已正确执行此操作。如果您已进行提交但尚未将自己添加到.mailmap 文件中,则会看到以下内容:
$ python bin/mailmap_check.py
This author is not included in the .mailmap file:
Joe Bloggs <joe@bloggs.com>
The .mailmap file needs to be updated because there are commits with
unrecognised author/email metadata.
For instructions on updating the .mailmap file see:
https://docs.sympy.org/dev/contributing/new-contributors-guide/workflow-process.html#mailmap-instructions
The following authors will be added to the AUTHORS file at the
time of the next SymPy release.
这意味着您应该将您的姓名和电子邮件地址添加到.mailmap 文件中。如果将此添加到文件末尾,则git diff将显示:
$ git diff
diff --git a/.mailmap b/.mailmap
index 3af6dc1..7fa63b1 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1307,3 +1307,4 @@ zsc347 <zsc347@gmail.com>
Øyvind Jensen <jensen.oyvind@gmail.com>
Łukasz Pankowski <lukpank@o2.pl>
彭于斌 <1931127624@qq.com>
+Joe Bloggs <joe@bloggs.com>
现在,您可以重新运行bin/mailmap_check.py脚本,您应该会看到:
$ python bin/mailmap_check.py
The mailmap file was reordered
For instructions on updating the .mailmap file see:
https://docs.sympy.org/dev/contributing/new-contributors-guide/workflow-process.html#mailmap-instructions
The following authors will be added to the AUTHORS file at the
time of the next SymPy release.
Joe Bloggs <joe@bloggs.com>
第一行表示.mailmap 文件已“重新排序”。这是因为文件应按字母顺序排列。脚本将移动您的名称到正确的位置,因此现在您可以看到更改如下所示:
$ git diff
diff --git a/.mailmap b/.mailmap
index 3af6dc1..7598d94 100644
--- a/.mailmap
+++ b/.mailmap
@@ -562,6 +562,7 @@ Joannah Nanjekye <joannah.nanjekye@ibm.com> Joannah Nanjekye <jnanjekye@python.o
Joannah Nanjekye <joannah.nanjekye@ibm.com> nanjekyejoannah <joannah.nanjekye@ibm.com>
Joaquim Monserrat <qmonserrat@mailoo.org>
Jochen Voss <voss@seehuhn.de>
+Joe Bloggs <joe@bloggs.com>
Jogi Miglani <jmig5776@gmail.com> jmig5776 <jmig5776@gmail.com>
Johan Blåbäck <johan_bluecreek@riseup.net> <johan.blaback@cea.fr>
Johan Guzman <jguzm022@ucr.edu>
现在,如果您重新运行脚本,您将看到:
$ python bin/mailmap_check.py
No changes needed in .mailmap
The following authors will be added to the AUTHORS file at the
time of the next SymPy release.
Joe Bloggs <joe@bloggs.com>
这里的关键信息是“在.mailmap 中不需要更改”,这意味着您已经正确更新了.mailmap 文件。您现在应该添加并提交这些更改:
git add .mailmap
git commit -m 'author: add Joe Bloggs to .mailmap'
``` ### 将用户名映射到 AUTHORS 文件条目
有时会使用错误的名称或电子邮件地址进行提交,或者作者会使用不同的名称和电子邮件地址进行多次提交,或者作者希望使用与其 GitHub 名称不同的适当名称。在这种情况下,应向.mailmap 文件添加一行,其中第一个名称和电子邮件地址是应在 AUTHORS 文件中记录的内容,而其他名称和电子邮件地址则是在其他提交中错误使用的名称和电子邮件地址。例如,如果提交记录的名称为`joeb`和电子邮件地址为`wrong@email.com`,但 AUTHORS 文件应显示如上所示的`Joe Bloggs`,则.mailmap 文件中应有如下一行:
```py
Joe Bloggs <joe@bloggs.com> joeb <wrong@email.com>
这种情况经常发生的一个原因是使用 GitHub 网页界面进行提交,它总是将名称记录为 GitHub 用户名,电子邮件地址类似于1785690389+joeb@users.noreply.github.com。在这种情况下,需要向.mailmap 文件添加一行,例如:
Joe Bloggs <joe@bloggs.com> joeb <1785690389+joeb@users.noreply.github.com>
多行文本可以添加到.mailmap 文件中。它们应记录作者使用过的所有不同名称和电子邮件地址组合,并将它们映射到一个在 AUTHORS 文件中显示的单个作者名字。
如果您的拉取请求已合并且之前尚未添加到 AUTHORS 文件,则在 SymPy 的下一个发布时将会添加您的名字。
写测试
原文:
docs.sympy.org/latest/contributing/new-contributors-guide/writing-tests.html
对于像 SymPy 这样的数学库来说,最重要的是正确性。函数永远不应返回数学上不正确的结果。正确性始终是首要关注点,即使这可能会牺牲性能或模块化。
因此,SymPy 中的所有功能都经过了广泛测试。本指南介绍了 SymPy 中测试的编写方法。
测试策略
为了确保高标准的正确性,SymPy 有以下适用于所有拉取请求的规则:
-
所有新功能必须经过测试。测试应该尽可能覆盖所有可能的情况以确保正确性。这意味着不仅要最大化代码覆盖率,还要覆盖所有可能的边界情况。
-
在合并之前,每个拉取请求必须通过所有测试。测试会在每个拉取请求上自动运行 GitHub Actions CI。如果任何测试失败,CI 将以红色❌失败。必须在合并拉取请求之前解决这些失败。
-
缺陷修复应该伴随着回归测试。
编写测试的基础知识
测试位于tests/目录中的代码旁边,文件名为test_<thing>.py。在大多数情况下,如果您修改了sympy/<submodule>/<file>.py,则该功能的测试将放在sympy/<submodule>/tests/test_<file>.py中。例如,sympy/simplify/sqrtdenest.py中函数的测试在sympy/simplify/tests/test_sqrtdenest.py中。有一些例外情况,因此通常尝试找到函数的现有测试所在位置,并将您的测试添加到其旁边。如果您为新功能添加测试,请遵循要添加到的模块中的测试的一般模式。
测试遵循一个简单的模式,从阅读现有测试文件中可以看出。测试在以test_开头的函数中,包含如下行
assert function(arguments) == result
例如
# from sympy/functions/elementary/tests/test_trigonometric.py
def test_cos_series():
assert cos(x).series(x, 0, 9) == \
1 - x**2/2 + x**4/24 - x**6/720 + x**8/40320 + O(x**9)
如果相关,新的测试案例可以添加到现有测试功能中,或者您可以创建一个新的测试功能。
运行测试
运行测试的基本方法是使用
./bin/test
运行测试,以及
./bin/doctest
运行 doctests。请注意,完整的测试套件可能需要一些时间才能运行,因此通常您应该只运行一部分测试,例如,对应于您修改的模块。您可以通过将子模块或测试文件的名称传递给测试命令来执行此操作。例如,
./bin/test solvers
仅运行求解器的测试。
如果您愿意,您也可以使用pytest来运行测试,而不是使用./bin/test工具,例如
pytest -m 'not slow' sympy/solvers
另一种选择是将您的代码推送到 GitHub,并让测试在 CI 上运行。GitHub Actions CI 将运行所有测试。但是,它可能需要一些时间才能完成,因此通常建议在提交之前至少运行基本测试,以避免等待。
在 GitHub Actions 上调试测试失败
当您在 CI 上看到测试失败时,例如
_____________________________________________________________________________________________________
_________________ sympy/printing/pretty/tests/test_pretty.py:test_upretty_sub_super _________________
Traceback (most recent call last):
File "/home/oscar/current/sympy/sympy.git/sympy/printing/pretty/tests/test_pretty.py", line 317, in test_upretty_sub_super
assert upretty( Symbol('beta_1_2') ) == 'β₁₂'
AssertionError
_________________之间的部分是测试的名称。您可以通过复制并粘贴此内容在本地复现测试:
./bin/test sympy/printing/pretty/tests/test_pretty.py::test_upretty_sub_super
或者
pytest sympy/printing/pretty/tests/test_pretty.py::test_upretty_sub_super
测试还显示了断言失败的文件和行号(在本例中为sympy/printing/pretty/tests/test_pretty.py的第 317 行),因此您可以查看以了解测试在测试什么。
有时当您执行此操作时,可能无法在本地复现测试失败。此类情况的一些常见原因包括:
-
您可能需要将最新的
master分支合并到您的分支以重现失败(GitHub Actions 在运行测试之前始终会将您的分支与最新的master合并)。 -
CI 测试环境与您的可能有所不同(特别是依赖于可选依赖项的测试)。检查 CI 日志顶部安装的相关软件包的版本。
-
可能是您之前运行的某些其他测试可能以某种方式影响了您的测试。SymPy 不应该具有全局状态,但有时可能会意外地引入某些状态。唯一检查这一点的方法是运行与 CI 上运行的完全相同的测试命令。
-
测试可能偶尔会失败。尝试多次重新运行测试。CI 上的测试日志开头打印了随机种子,可以传递给
./bin/test --seed,以及可能有助于重现此类失败的PYTHONHASHSEED环境变量。
有时 CI 上的失败可能与您的分支无关。我们只合并通过 CI 的分支,因此理想情况下,主分支始终具有通过的测试。但有时失败可能会发生。通常情况下,这要么是因为失败是偶发的(参见上一个项目符号),并且没有注意到,要么是因为某些可选依赖项已更新,这会破坏可选依赖项测试。如果测试失败似乎与您的更改无关,请检查主分支的CI 构建以及其他最近的 PR 是否具有相同的失败。如果是这样,那么很可能如此。如果不是,请仔细检查您的更改是否导致失败,即使看起来与此无关。
当主分支中的 CI 失败时,请注意在修复之前无法合并您的拉取请求。这不是必需的,但如果您知道如何修复,请这样做以帮助所有人(如果这样做,请在单独的拉取请求中执行,以便可以迅速合并)。
回归测试
回归测试是指在修复错误之前会失败但现在通过的测试。通常,您可以使用问题示例中的代码示例作为测试用例,尽管也可以简化这些示例或编写自己的示例,只要测试问题本身。
例如,考虑问题 #21177,该问题确定了以下错误结果:
>>> residue(cot(pi*x)/((x - 1)*(x - 2) + 1), x, S(3)/2 - sqrt(3)*I/2)
-sqrt(3)*tanh(sqrt(3)*pi/2)/3
>>> residue(cot(pi*x)/(x**2 - 3*x + 3), x, S(3)/2 - sqrt(3)*I/2)
0
在此,第一个表达式是正确的,但第二个表达式是错误的。在问题中,问题的根源被确定在as_leading_term方法中,并且还发现了几个其他相关问题。
在相应的拉取请求(#21253)中,添加了几个回归测试。例如(从该 PR 中):
# In sympy/functions/elementary/tests/test_trigonometric.py
def test_tan():
...
# <This test was already existing. The following was added to the end>
# https://github.com/sympy/sympy/issues/21177
f = tan(pi*(x + S(3)/2))/(3*x)
assert f.as_leading_term(x) == -1/(3*pi*x**2)
# In sympy/core/tests/test_expr.py
def test_as_leading_term():
...
# <This test was already existing. The following was added to the end>
# https://github.com/sympy/sympy/issues/21177
f = -3*x + (x + Rational(3, 2) - sqrt(3)*S.ImaginaryUnit/2)**2\
- Rational(3, 2) + 3*sqrt(3)*S.ImaginaryUnit/2
assert f.as_leading_term(x) == \
(3*sqrt(3)*x - 3*S.ImaginaryUnit*x)/(sqrt(3) + 3*S.ImaginaryUnit)
# https://github.com/sympy/sympy/issues/21245
f = 1 - x - x**2
fi = (1 + sqrt(5))/2
assert f.subs(x, y + 1/fi).as_leading_term(y) == \
(-36*sqrt(5)*y - 80*y)/(16*sqrt(5) + 36)
# In sympy/series/tests/test_residues.py
def test_issue_21177():
r = -sqrt(3)*tanh(sqrt(3)*pi/2)/3
a = residue(cot(pi*x)/((x - 1)*(x - 2) + 1), x, S(3)/2 - sqrt(3)*I/2)
b = residue(cot(pi*x)/(x**2 - 3*x + 3), x, S(3)/2 - sqrt(3)*I/2)
assert a == r
assert (b - a).cancel() == 0
此示例显示了回归测试的一些重要方面:
-
应添加用于修复根本问题的测试,而不仅仅是最初报告的问题。例如,此示例中最初报告的问题是
residue()函数,但根本问题是as_leading_term()方法。 -
同时,还可以有利于添加用于报告的高级问题的测试。这确保了即使其实现细节发生变化而不再使用已修复的代码路径,
residue本身也不会在未来出现问题。 -
此示例未显示,但在某些情况下,为测试用例简化最初报告的问题可能是明智的选择。例如,有时用户会在报告中包含不必要的细节,这些细节对问题的重现实际上并不重要(例如,符号上的不必要假设),或者使输入表达式过于复杂或包含太多不必要的常数符号。如果最初报告的代码运行速度慢,尤其重要。如果可以用更快执行的测试来测试相同的内容,则应优先考虑此选项。
-
回归测试还应添加用于在问题中标识的其他错误。例如,此示例中第二个测试(添加到
test_as_leading_term()的测试)被确定为问题评论中的相关问题(评论链接)。 -
在回归测试中交叉引用问题编号非常有用,无论是使用注释还是在测试名称中。如果将测试添加到现有测试中,则更倾向于使用注释。
回归测试不仅用于修复错误。它们还应该用于新功能,以确保新实现的功能保持正确和稳定。
特殊类型的测试
大多数测试将采用assert function(input) == output的形式。然而,有些需要测试的事物应该以特定方式进行测试。
测试异常
要测试函数是否引发给定异常,请使用sympy.testing.pytest.raises。raises()接受异常类和 lambda 表达式。例如
from sympy.testing.pytest.raises
raises(TypeError, lambda: cos(x, y)
记得包括lambda。否则,代码将立即执行并引发异常,导致测试失败。
# BAD
raises(TypeError, cos(x, y)) # This test will fail
raises也可以作为上下文管理器使用,例如
with raises(TypeError):
cos(x, y)
但是,使用此形式时要小心,因为它只能检查一个表达式。如果上下文管理器下的代码引发多个异常,则实际上只会测试第一个异常。
# BAD
with raises(TypeError):
cos(x, y)
sin(x, y) # THIS WILL NEVER BE TESTED
lambda 形式通常更好,因为它避免了这个问题,尽管如果你要测试无法用 lambda 表示的内容,则需要使用上下文管理器形式。
测试警告
可以使用sympy.testing.pytest.warns()上下文管理器来测试警告。请注意,SymPyDeprecationWarning 是特殊的,应该使用 warns_deprecated_sympy() 进行测试(参见下文)。
上下文管理器应该接受警告类(warnings.warn() 默认使用 UserWarning),以及可选的正则表达式,用作 match 关键字参数来匹配警告消息。
from sympy.testing.pytest import warns
with warns(UserWarning):
function_that_emits_a_warning()
with warns(UserWarning, match=r'warning'):
function_that_emits_a_warning()
任何发出警告的测试功能都应该使用 warns()。 这样,在测试过程中实际上不会发出任何警告。这包括来自外部库的警告。
SymPy 本身应该非常谨慎地使用警告。除了弃用警告之外,SymPy 通常不使用警告,因为对于使用 SymPy 作为库的用户来说,这些警告可能会过于烦人,不值得。
当使用它们时,必须设置警告的 stacklevel 参数,以便显示调用引发警告函数的用户代码。如果无法正确设置 stacklevel 参数,则使用 warns(test_stacklevel=False) 来禁用 warns 中对正确使用 stacklevel 的检查。如果这适用于 SymPyDeprecationWarning,则必须使用 warns(SymPyDeprecationWarning, test_stacklevel=False) 替代 warns_deprecated_sympy()。### 测试弃用功能
应该使用sympy.testing.pytest.warns_deprecated_sympy()上下文管理器来测试弃用功能。
此上下文管理器的唯一目的是测试弃用警告本身是否正常工作。这应该是测试套件中唯一一个调用弃用功能的地方。所有其他测试应该使用非弃用功能。如果无法避免使用弃用功能,则可能表明实际上不应该弃用该功能。
弃用策略页面详细说明了如何向函数添加弃用。
例如,
from sympy.testing.pytest import warns_deprecated_sympy
x = symbols('x')
# expr_free_symbols is deprecated
def test_deprecated_expr_free_symbols():
with warns_deprecated_sympy():
assert x.expr_free_symbols == {x}
如果代码使用另一个库的已弃用功能,则应更新该代码。 在此之前,应在相应的测试中使用常规的 warns() 上下文管理器以防止发出警告。
检测某些东西是否保持不变
普通测试样式
assert function(input) == output
对大多数测试都有效。 但是,在 SymPy 对象应保持不变的情况下不起作用。 考虑以下示例:
assert sin(pi) == 0
assert sin(pi/2) == 1
assert sin(1) == sin(1)
这里的前两个测试很好。 测试 sin 是否为输入 pi 和 pi/2 返回相应的特殊值。 但是,最后一个测试名义上检查 sin(1) 不返回任何东西。 但仔细检查后,我们发现它根本没有这样做。 sin(1) 实际上可以返回任何东西。 它可以返回完全荒谬的内容,甚至是错误的答案,如 0。 测试仍然会通过,因为它只是检查 sin(1) 的结果是否等于 sin(1) 的结果,这总是会成立的,只要它总是返回相同的东西。
我们真的想检查 sin(1) 保持不变。 sympy.core.expr.unchanged 助手将会做到这一点。
使用方法如下
from sympy.core.expr import unchanged
def test_sin_1_unevaluated():
assert unchanged(sin, 1)
现在,这个测试实际上检查了正确的内容。 如果 sin(1) 被设置为返回某个值,则测试将失败。
使用 Dummy 进行表达式测试
返回 Dummy 的表达式不能直接使用 == 进行测试,因为 Dummy 的特性。 在这种情况下,请使用 dummy_eq() 方法。 例如:
# from
sympy/functions/combinatorial/tests/test_comb_factorials.py
def test_factorial_rewrite():
n = Symbol('n', integer=True)
k = Symbol('k', integer=True, nonnegative=True)
assert factorial(n).rewrite(gamma) == gamma(n + 1)
_i = Dummy('i')
assert factorial(k).rewrite(Product).dummy_eq(Product(_i, (_i, 1, k)))
assert factorial(n).rewrite(Product) == factorial(n)
一致性检查
仅通过已知输入和输出的集合来测试可以有所限制。 例如
assert function(input) == expression
将检查 function(input) 返回 expression,但不检查 expression 本身是否数学上正确。
但是,根据 function 的不同,有时可以进行一致性检查,以验证 expression 本身是否正确。 这通常归结为“以两种不同的方式计算 expression”。 如果两种方式一致,则它正确的可能性很高,因为两种完全不同的方法产生相同错误答案的可能性很小。
例如,不定积分的反函数是微分。 可以通过检查结果的导数是否产生原始被积函数来验证积分的一致性:
expr = sin(x)*exp(x)
expected == exp(x)*sin(x)/2 - exp(x)*cos(x)/2
# The test for integrate()
assert integrate(expr, x) == expected
# The consistency check that the test itself is correct
assert diff(expected, x) == expr
与 integrate 相比,diff 的实现非常简单,并且已经单独进行了测试,因此可以确认答案是正确的。
当然,也可以手动确认答案,这是 SymPy 中大多数测试所做的。 但是一致性检查并不会有什么坏处,尤其是当它很容易做到时。
在 SymPy 测试套件中使用一致性检查本身并不一致。一些模块大量使用它们,例如 ODE 模块中的每个测试都使用checkodesol()进行自我检查。而其他模块在其测试中根本不使用一致性检查,尽管其中一些可以更新以执行此操作。在某些情况下,没有合理的一致性检查方法,必须使用其他真实来源验证测试输出。
在大量使用一致性检查时,通常最好将逻辑提取到测试文件中的辅助函数中,以避免重复。辅助函数应该以下划线开头,这样它们不会被测试运行程序误认为是测试函数。
随机测试
另一种测试自我一致性的方法是在随机数输入上检查表达式。可以使用sympy.core.random中的辅助函数来实现这一点。请参阅在sympy/functions/special/中大量使用此功能的测试。
如果添加了一个随机测试,请确保多次运行测试以确保测试始终通过。可以通过使用打印在测试顶部的随机种子来复现随机测试。例如
$./bin/test
========================================================================== test process starts ==========================================================================
executable: /Users/aaronmeurer/anaconda3/bin/python (3.9.13-final-0) [CPython]
architecture: 64-bit
cache: yes
ground types: gmpy 2.1.2
numpy: 1.22.4
random seed: 7357232
hash randomization: on (PYTHONHASHSEED=3923913114)
这里的随机种子是7357232。可以通过以下方法复现:
./bin/test --seed 7357232
一般来说,为了复现随机测试失败,您可能需要使用与测试头部显示的相同的 Python 版本和架构。在某些情况下,为了复现随机失败的测试,您可能还需要使用完全相同的输入参数运行测试(即运行完整的测试套件或仅运行子集)。
跳过测试
测试可以使用sympy.testing.pytest.SKIP装饰器或使用sympy.testing.pytest.skip()函数来跳过。请注意,由于预期失败而跳过的测试应该使用@XFAIL装饰器(参见下文)。因为测试速度太慢而跳过的测试应该使用@slow装饰器。
应避免无条件跳过的测试。这样的测试几乎完全无用,因为它实际上永远不会被运行。无条件跳过测试的唯一原因是,如果有其他原因无法使用@XFAIL或@slow装饰器。
@SKIP()和skip()都应包含解释为何跳过测试的消息,例如skip('numpy not installed')。
跳过测试的典型用法是当测试依赖于可选依赖项时。
这类测试通常写成
from sympy.external import import_module
# numpy will be None if NumPy is not installed
numpy = import_module('numpy')
def test_func():
if not numpy:
skip('numpy is not installed')
assert func(...) == ...
当以这种方式编写测试时,如果没有安装 NumPy,测试不会失败,这很重要,因为 NumPy 不是 SymPy 的硬依赖项。另请参阅使用外部依赖项编写测试。 ### 将测试标记为预期失败
SymPy 中的一些测试预期会失败。它们被设计为,在实现检查功能时,测试已经为其编写。
预期失败的测试称为 XFAIL 测试。当它们如预期般失败时,它们将显示为测试运行器中的f,而当它们通过时则显示为X(或“XPASS”)。一个 XPASS 测试应该移除其@XFAIL装饰器,以使其成为正常测试。
要标记一个测试为 XFAIL,请将sympy.testing.pytest.XFAIL装饰器添加到其中。
from sympy.testing.pytest import XFAIL
@XFAIL
def test_failing_integral():
assert integrate(sqrt(x**2 + 1/x**2), x) == x*sqrt(x**2 + x**(-2))*(sqrt(x**4 + 1) - atanh(sqrt(x**4 + 1)))/(2*sqrt(x**4 + 1))
编写 XFAIL 测试时需要注意,确保在功能启用时它能够通过。例如,如果误输入输出,则该测试可能永远无法通过。例如,上述测试中的积分可能开始起作用,但返回的结果形式可能与正在检查的形式略有不同。更健壮的测试应该是:
from sympy.testing.pytest import XFAIL
@XFAIL
def test_failing_integral():
# Should be x*sqrt(x**2 + x**(-2))*(sqrt(x**4 + 1) - atanh(sqrt(x**4 + 1)))/(2*sqrt(x**4 + 1))
assert not integrate(sqrt(x**2 + 1/x**2), x).has(Integral)
一旦积分开始工作,这将导致测试 XPASS,届时测试可以更新为integrate()的实际输出(可以与预期输出进行比较)。### 标记测试为慢
一个运行缓慢的测试应该用来自sympy.testing.pytest.slow的@slow装饰器标记。@slow装饰器应该用于运行时间超过一分钟的测试。挂起的测试应该使用@SKIP而不是@slow。慢测试将在单独的 CI 作业中自动运行,但默认情况下会被跳过。你可以手动运行慢测试,方法如下:
./bin/test --slow
``` ### 使用外部依赖项编写测试
在为使用 SymPy 的一个可选依赖项的函数编写测试时,应该以一种方式编写测试,使得在未安装模块时该测试不会失败。
这样做的方法是使用`sympy.external.import_module()`。如果已安装,则导入模块,否则返回`None`。
当涉及模块未安装时,应使用`sympy.testing.pytest.skip`来跳过测试(参见跳过测试)。如果整个测试文件应该跳过,可以在模块级别执行此操作,或者在每个单独的函数中执行。
您还应确保在“可选依赖项”CI 运行中运行测试。要做到这一点,请编辑`bin/test_optional_dependencies.py`,确保包含测试(大多数测试 SymPy 子模块的可选依赖项已自动包含)。
如果可选依赖项是新的,请将其添加到在`.github/workflows/runtests.yml`中的可选依赖项构建的安装列表,并将其添加到`doc/src/contributing/dependencies.md`的可选依赖项文档中。
当使用`mpmath`时,不需要执行任何这些操作,因为它已经是 SymPy 的硬依赖项,并且将始终安装。
每个公共函数应该有文档字符串,每个文档字符串都应该有示例。代码示例都是经过测试的,这也是它们有时被称为*文档测试*的原因。文档字符串风格指南详细介绍了如何在文档字符串中格式化示例的更多细节。
要运行文档测试,请使用
```py
./bin/doctest
命令。此命令还可以带参数来测试特定文件或子模块,类似于bin/test。
文档测试应该以一种自包含的方式编写,每个文档测试都像一个新的 Python 会话。这意味着每个文档测试必须手动导入在文档测试中使用的每个函数,并定义使用的符号。这看起来可能有些啰嗦,但对于对 SymPy 甚至 Python 都不熟悉的用户来说是有帮助的。它还使得用户可以轻松地将示例复制粘贴到他们自己的 Python 会话中(HTML 文档中的每个代码示例的右上角都包含一个按钮,用于将整个示例复制到剪贴板)。
例如
>>> from sympy import Function, dsolve, cos, sin
>>> from sympy.abc import x
>>> f = Function('f')
>>> dsolve(cos(f(x)) - (x*sin(f(x)) - f(x)**2)*f(x).diff(x),
... f(x), hint='1st_exact')
Eq(x*cos(f(x)) + f(x)**3/3, C1)
文档测试的输出应该与在python会话中看到的完全一样,输入前有>>>,输出后有结果。文档测试器检查输出字符串是否匹配,不像通常使用==检查 Python 对象是否相同的测试那样。因此,输出需要完全与 Python 会话中的一样。
像测试一样,所有的文档测试都必须通过才能接受更改。但是,在编写文档测试时,重要的是要记住文档测试不应被视为测试。相反,它们是经过测试的示例。
因此,在编写文档测试时,应始终考虑如何编写一个好的、易读的示例。文档测试不需要广泛覆盖所有可能的输入,并且不应包含边界或极端情况,除非这些情况对用户有重要意义。
在文档测试中测试的所有内容也应在正常测试中进行测试。如果改进文档,则随时可以自由删除或更改文档测试示例(相比之下,正常测试在某些特殊情况下以外的情况下不应更改或删除)。
这也意味着,文档测试应首先以一种使得它们可以被阅读文档的人理解的方式编写。有时可能会诱人以某种间接的方式编写文档测试,以满足文档测试器的要求,但如果这样做使示例变得更难理解,则应避免。例如
# BAD
>>> from sympy import sin, cos, trigsimp, symbols
>>> x = symbols('x')
>>> result = trigsimp(sin(x)*cos(x))
>>> result == sin(2*x)/2
True
这通过了文档测试,而类似这样的内容在正常测试中是可以接受的。但在文档字符串示例中,直接显示实际输出会更清晰。
# BETTER
>>> from sympy import sin, cos, trigsimp, symbols
>>> x = symbols('x')
>>> trigsimp(sin(x)*cos(x))
sin(2*x)/2
当然,在某些情况下,完整的输出过于笨重,显示它会使示例更难阅读,所以这种情况可能是合适的。在做出决定时,请慎重考虑,记住文档示例的可理解性是最重要的事情。在极端情况下,可能更倾向于跳过测试示例而不是为了迎合文档测试而写成难以阅读的方式(见下文)。
这里有一些编写文档测试的额外提示:
-
可以通过使用
...作为续行提示将长输入行分成多行,如上例所示。文档测试运行器还允许将长输出行进行换行(忽略输出中的换行符)。 -
常见的符号名称可以从
sympy.abc导入。不常见的符号名称或需要使用假设的符号应该使用symbols进行定义。>>> from sympy.abc import x, y >>> x + y x + y>>> from sympy import symbols, sqrt >>> a, b = symbols('a b', positive=True) >>> sqrt((a + b)**2) a + b -
如果测试显示了回溯信息,则应将
Traceback (most recent call last):和最后一行异常消息之间的所有内容替换为...,例如>>> from sympy import Integer >>> Integer('a') Traceback (most recent call last): ... ValueError: invalid literal for int() with base 10: 'a' -
...是特殊的,每当它出现在示例的输出中时,文档测试器都允许其替换任意数量的文本。在确切输出在运行之间不同的情况下,也应使用它,例如>>> from sympy import simplify >>> simplify <function simplify at ...>这里实际输出类似于
<function simplify at 0x10e997790>,但0x10e997790是一个内存地址,每个 Python 会话都会不同。输出中的
...应该谨慎使用,因为它会阻止文档测试实际检查输出的那部分。对文档的读者来说,可能不清楚它的含义。请注意,如果将来文档测试的输出更新为其他内容是可以的。...不应用于试图“未来保护”文档测试输出。还请注意,文档测试器已经自动处理输出中的空白差异和浮点数值。 -
您可以在输出行中进行换行。文档测试器会自动忽略输出中的空白差异,包括换行符。长行应该被打断,以避免在 HTML 文档中超出页面(并确保源代码行不超过 80 个字符)。例如:
>>> ((x + 1)**10).expand() x**10 + 10*x**9 + 45*x**8 + 120*x**7 + 210*x**6 + 252*x**5 + 210*x**4 + 120*x**3 + 45*x**2 + 10*x + 1 -
如果文档测试不能通过,另一种选择是通过在输入行的末尾添加
# doctest:+SKIP来跳过它,例如>>> import random >>> random.random() # doctest: +SKIP 0.6868680200532414# doctest:+SKIP部分会在 HTML 文档中自动隐藏。在跳过文档测试时,务必手动测试输出,因为文档测试器不会为您检查它。应该谨慎使用
# doctest:+SKIP。理想情况下,只有当无法运行时才应跳过文档测试。跳过的文档测试永远不会被测试,这意味着它可能会过时(即不正确),这会让用户感到困惑。 -
需要依赖项才能运行的 doctest 不应该用
# doctest: +SKIP跳过。相反,应该在函数上使用@doctest_depends_on装饰器来指示为了运行 doctest 应该安装哪些库。 -
如果测试输出包含空行,请用
<BLANKLINE>代替空行。否则,doctester 会认为输出在空行结束。<BLANKLINE>会在 HTML 文档中自动隐藏。这种情况并不常见,因为大多数 SymPy 对象不会打印出空行。 -
避免在 doctest 示例中使用
pprint()。如果你需要以更易读的方式显示表达式,可以使用美元符号内联包含 LaTeX 数学。如果你绝对必须使用pprint(),请始终使用pprint(use_unicode=False),因为用于漂亮打印的 Unicode 字符在 HTML 文档中的呈现不总是正确的。 -
如果你想显示某些东西返回
None,可以使用print,比如>>> from sympy import Symbol >>> x = Symbol('x', positive=True) >>> x.is_real True >>> x = Symbol('x', real=True) >>> x.is_positive # Shows nothing, because it is None >>> print(x.is_positive) None -
你可以在 doctest 中添加简短的注释,可以是在一行的末尾或者在
>>>之后单独使用。然而,这些注释通常应该只有几个词。关于 doctest 中发生的事情的详细解释应该放在周围的文本中。 -
字典和集合会被 doctester 自动排序,任何表达式都会自动排序,以便术语的顺序总是以相同的方式打印。通常你可以只包含 doctester“预期”的输出,它将随后总是通过。
>>> {'b': 1, 'a': 2} {'a': 2, 'b': 1} >>> {'b', 'a'} {'a', 'b'} >>> y + x x + y ``` ## 更新现有测试
有时候当你改变了某些东西或者修复了一个 bug,一些现有的测试会失败。如果这种情况发生,你应该检查测试看看为什么会失败。在许多情况下,测试将检查你没有考虑到的东西,或者你的变更具有意外的副作用破坏了其他东西。当这种情况发生时,你可能需要重新审视你的变更。如果你不确定该怎么做,你应该在问题或拉取请求上讨论一下。
如果失败的测试是一个代码质量测试,通常意味着你只需要修复代码以满足代码质量检查(例如,删除尾随空白)。
不过,偶尔会发生测试失败但没有任何问题的情况。这种情况下,应该更新测试。最常见的情况是检查特定表达式的测试,但是函数现在返回一个不同但在数学上等价的表达式。这在 doctests 中特别常见,因为它们不仅检查输出表达式,还检查打印方式。
如果一个函数的输出在数学上是等价的,现有的测试可以用新的输出进行更新。但是,即使这样做,你也应该小心:
-
仔细检查新输出确实是相同的。手动检查像是如果旧表达式和新表达式的差异简化为 0。有时,两个表达式对于某些假设是等价的,但不是对于所有的,因此检查这两个表达式对于所有复数确实是相同的。这特别可能发生在涉及平方根或其他根的表达式中。你可以检查随机数,或使用
equals()方法来做到这一点。 -
如果新的输出比旧输出复杂得多,那么即使在数学上它们是等价的,更新测试也可能不是一个好主意。相反,你可能需要调整更改,使函数仍然返回更简单的结果。
-
这不常见,但确实可能发生现有测试本身是错误的情况。如果一个测试是明显错误的,应该删除并更新。
无论如何,在更新现有测试时,你应该总是在提交消息或拉取请求评论中解释做出此更改的原因。不要在代码注释或文档中解释更改。代码注释和文档应该只涉及当前的代码。关于更改的讨论应该放在提交消息或问题跟踪器中。关于代码曾经如何的代码注释只会变得令人困惑,并且在更改后实际上不再相关。
同样,默认情况下不要更改现有的测试。这些测试存在是有原因的,改变它们会背离最初的目的。这条规则的例外是 doctests,如果它们改进了文档,可以允许它们被更改或删除,因为 doctests 的主要目的是为用户提供示例。 ## 代码质量检查
SymPy 有几个必须通过的代码质量检查。在拉取请求上运行的第一个任务是代码质量检查。如果此任务失败,其他测试都不会运行。直到它们被修复,你的 PR 可能会被审阅者忽略。
代码质量检查都很容易修复。你可以在本地运行检查,
./bin/test quality
和
flake8 sympy
第二个命令需要你安装flake8。确保你安装了最新版本的 flake8 及其依赖项pycodestyle和pyflakes。有时,这些包的新版本会添加新的检查,如果你安装了旧版本,你将看不到这些检查。
./bin/test quality检查非常基本的代码质量问题。导致测试失败的最常见问题是尾随空格。尾随空格是指代码行末尾有空格。这些空格无任何作用,只会污染代码差异。处理尾随空格的最佳方法是配置文本编辑器在保存时自动去除尾随空格。你也可以在 SymPy 仓库中使用./bin/strip_whitepace命令。
flake8 命令会检查代码中的基本错误,如未定义变量。这些错误由 setup.cfg 中的配置限制,仅检查逻辑错误。通常情况下,flake8 检查的代码风格错误是禁用的。在罕见情况下,flake8 的警告可能是误报。如果发生这种情况,请在相应行添加 # noqa: <CODE> 注释,其中 <CODE> 是来自 flake8.pycqa.org/en/latest/user/error-codes.html 的错误代码。例如,使用 multipledispatch 的代码将需要使用
@dispatch(...)
def funcname(arg1, arg2): # noqa: F811
...
@dispatch(...)
def funcname(arg1, arg2): # noqa: F811
...
避免关于多次重新定义相同函数的警告。
测试风格指南
在大多数情况下,测试应该以与同一测试文件中周围测试相匹配的方式编写。
在编写测试时应遵循一些重要的风格点:
-
测试函数应以
test_开头。如果不是,测试运行器将不会测试它们。任何不是测试函数的辅助函数不应以test_开头。通常最好将测试辅助函数以下划线开头。如果发现自己在多个测试文件中重用相同的辅助函数,请考虑是否应将其移动到类似sympy.testing的地方。 -
使用与
str()生成的相同空白字符格式化表达式(例如,在二进制+和-周围加上空格,*和**周围不加空格,逗号后面加空格,不要冗余的括号等) -
避免在测试用例中使用浮点值。除非测试明确测试了浮点输入上的函数结果,否则测试表达式应使用精确值。
特别是要避免使用像
1/2这样会创建浮点值的整数除法(参见 教程的注意事项部分)。例如:# BAD assert expand((x + 1/2)**2) == x**2 + x + 1/4# GOOD assert expand((x + S(1)/2)**2) == x**2 + x + S(1)/4如果你确实打算显式测试一个带有浮点值的表达式,请使用浮点数(如
0.5而不是1/2),这样可以清楚表明这是有意为之而非意外发生。 -
符号可以在测试文件顶部或每个测试函数内定义。在测试文件顶部定义带有假设的符号应命名为明确表明它们具有假设的方式(例如,
xp = Symbol('x', positive=True))。通常最好在每个测试函数内定义具有假设的符号,以免它们被意外地重用在其他不希望它们具有定义假设的测试中(这通常会改变测试的行为)。 -
测试文件通常以它们测试的代码文件命名(例如,
sympy/core/tests/test_symbol.py包含对sympy/core/symbol.py的测试)。然而,如果有些测试与特定的代码文件并不完全对应,这个规则是可以打破的。 -
在测试中避免使用表达式的字符串形式(显然在打印测试中应该使用字符串;这条规则适用于其他类型的测试)。这会使测试依赖于精确的打印输出,而不仅仅是表达式的输出。这会使测试难以阅读,并且如果打印机以某种方式更改,测试就需要更新。
例如:
# BAD assert str(expand((x + 2)**3)) == 'x**3 + 6*x**2 + 12*x + 8'# GOOD assert expand((x + 2)**3) == x**3 + 6*x**2 + 12*x + 8同样地,不要解析表达式的字符串形式作为输入(除非测试明确测试解析字符串)。直接创建表达式即可。即使这需要创建许多符号或广泛使用
S()来包装有理数,这仍然更清晰。# BAD expr = sympify('a*b*c*d*e') assert expr.count_ops() == 4# GOOD a, b, c, d, e = symbols('a b c d e') expr = a*b*c*d*e assert expr.count_ops() == 4 -
在测试假设时使用
is True、is False和is None。不要依赖真值性,因为很容易忘记None在 Python 中被视为假。# BAD assert not x.is_real# GOOD assert x.is_real is False
测试覆盖率
要生成测试覆盖报告,首先安装coverage.py(例如,使用pip install coverage)。然后运行
./bin/coverage_report.py
这将运行测试套件并分析代码库中哪些行至少被一个测试覆盖。请注意,这比使用./bin/test正常运行测试需要更长时间,因为覆盖工具会使 Python 运行稍慢。您也可以运行测试的子集,例如./bin/coverage_report.py sympy/solvers。
一旦测试完成,覆盖报告将位于covhtml中,您可以通过打开covhtml/index.html来查看。每个文件将显示哪些行被测试覆盖(绿色显示),哪些行没有被任何测试覆盖(红色显示)。
如果可能的话,应为未被任何测试覆盖的行添加测试。注意,通常不可能实现 100%的覆盖率。可能会有一行防御性代码,用于检查是否出现错误,但仅在出现错误时才会触发。或者可能会有一些与外部依赖交互的代码,或者只有在安装了特定的可选依赖项时才会触发。然而,如果一行代码可以测试,就应该进行测试。例如,测试文件本身应该实现 100%的覆盖率。如果测试文件中的一行未被覆盖,通常这表示一个错误(参见nedbatchelder.com/blog/202008/you_should_include_your_tests_in_coverage.html)。
还要注意,覆盖率并不是结束的全部。虽然未测试的代码行无法保证正确性,但覆盖的代码行也不一定正确。有时代码可能有条件,比如if a or b,并且在每个测试中a总是为真,所以b条件从未被测试过。当然,仅因为代码行被执行,并不意味着它是正确的。测试需要实际检查函数的输出是否符合预期。测试覆盖率只是确保代码库正确性的一部分。参见nedbatchelder.com/blog/200710/flaws_in_coverage_measurement.html。
假设测试
现在可以使用Hypothesis库创建基于属性的测试。测试应添加到相应的tests子目录中的test_hypothesis.py文件中。如果文件不存在,请创建一个。以下是模数算术的假设测试示例:
from hypothesis import given
from hypothesis import strategies as st
from sympy import symbols
from sympy import Mod
@given(a = st.integers(), p = st.integers().filter(lambda p: p != 0), i = st.integers(),
j = st.integers().filter(lambda j: j != 0))
def test_modular(a, p, i, j):
x, y = symbols('x y')
value = Mod(x, y).subs({x: a, y: p})
assert value == a % p
构建文档
原文链接:
docs.sympy.org/latest/contributing/new-contributors-guide/build-docs.html
首先安装文档所需的依赖项。
所需的依赖项
你可以在本地安装依赖项,或者构建一个包含这些依赖项的 Docker 镜像。
Docker
如果你有 Docker,则可以选择构建 Docker 镜像而不是按照以下特定于操作系统的安装说明:
cd doc
docker build -f Dockerfile.htmldoc -t sympy_htmldoc .
如果选择此选项,现在可以跳到下面的“构建文档”部分。
Debian/Ubuntu
对于 Debian/Ubuntu:
apt-get install python3-sphinx texlive-latex-recommended dvipng librsvg2-bin imagemagick docbook2x graphviz
使用以下命令安装 pip:
sudo apt install python3-pip
但是,你也可以创建一个虚拟环境,在其中使用 pip:
python3 -m venv /path/to/my/venv # create the venv
然后激活它:
source /path/to/my/venv/bin/activate # need to rerun this each time you open a new terminal
通过上述两种方法安装 pip 后,运行:
python -m pip install -r doc/requirements.txt
如果出现 mpmath 错误,请安装 python-mpmath 软件包:
apt-get install python-mpmath
如果出现 matplotlib 错误,请安装 python-matplotlib 软件包:
apt-get install python-matplotlib
Fedora
对于 Fedora(以及可能其他基于 RPM 的发行版),安装先决条件:
dnf install python3-sphinx librsvg2 ImageMagick docbook2X texlive-dvipng-bin
texlive-scheme-medium librsvg2-tools
python -m pip install -r doc/requirements.txt
如果出现 mpmath 错误,请安装 python3-mpmath 软件包:
dnf install python3-mpmath
如果出现 matplotlib 错误,请安装 python3-matplotlib 软件包:
dnf install python3-matplotlib
Mac
对于 Mac,首先安装 homebrew:brew.sh/
使用 homebrew 安装这些软件包:
brew install imagemagick graphviz docbook librsvg
使用 pip 或 conda 安装文档所需的依赖项:
python -m pip install -r requirements.txt
或者:
conda install -c conda-forge --file requirements.txt
在 Windows 系统上使你的 Sphinx 构建成功有些棘手,因为某些依赖项如 dvipng 或 docbook2x 不可用。
Windows 10
对于 Windows 10,可以通过 Windows Subsystem for Linux 来解决,按照下面的教程安装 Ubuntu shell 在你的 Windows 系统上:
learn.microsoft.com/zh-cn/windows/wsl/install
在命令提示符中,运行 ubuntu 转到 Linux 终端,并按上面的 Debian/Ubuntu 教程安装依赖项,然后可以运行 make html 进行构建。(请注意,还必须通过 apt-get install make 安装 make。)
如果你想在 Windows 文件系统中的 SymPy 工作文件夹中更改目录,你可以在文件路径前面加上 cd /mnt/,然后在你的 shell 中运行以导航到文件夹。(还请注意,Linux 使用 / 而不是 \ 作为文件路径的分隔符。)
此方法比 Cygwin 或 MSYS2 更兼容,并且比虚拟机更方便,如果你的工作流程部分需要 Linux 环境,则此方法仅适用于 Windows 10 64 位用户。
或者
按照 指南 安装 Chocolatey
安装 make 和其他依赖项:
choco install make graphviz rsvg-convert imagemagick
安装 python 依赖:
pip install -r doc/requirements.txt
构建文档
Docker
如果选择使用 Docker 构建,并按上述说明构建 sympy_htmldoc 镜像,则可以使用以下命令构建文档:
docker run --rm -v /absolute/path/to/sympy:/sympy sympy_htmldoc
(确保替换实际的 sympy 绝对文件系统路径!)此命令可以从任何目录运行。
本地安装
如果您选择按照上述特定于操作系统的说明进行操作并在本地安装所需的依赖项,则可以通过运行doc子目录中的makefile来构建文档:
cd doc
make html
查看文档
构建文档后,生成的文件将位于doc/_build/html下。要在您喜欢的 Web 浏览器中查看它们,请使用下拉菜单并选择“打开文件”,导航到sympy/doc/_build/html文件夹,然后打开index.html文件。
使用实时服务器进行自动重建
上述说明告诉您如何构建一次文档并在浏览器中加载它们。在对文档源进行更改后,您将需要手动重复构建步骤,并在浏览器中重新加载页面。
还有一种替代方法,可以设置一个实时服务器,它将监视文档目录,在检测到更改时自动重建,并自动重新加载您在浏览器中查看的页面。
如果您想使用此选项,则过程再次取决于您是使用 Docker 还是本地安装。
Docker
要使用 Docker 启动实时服务器,可以使用:
docker run --rm -it \
-v /absolute/path/to/sympy:/sympy \
-p 8000:80 \
sympy_htmldoc live
然后在浏览器中导航到localhost:8000。您可以通过更改命令中的8000来使用不同的端口。同样,请确保替换实际的 sympy 绝对文件系统路径。
完成后,您可以在终端中使用ctrl-c停止服务器。
或者,您可以在后台模式下运行服务器,使用:
docker run --rm -d --name=sympy-livehtml \
-v /absolute/path/to/sympy:/sympy \
-p 8000:80 \
sympy_htmldoc live
然后使用以下命令停止它:
docker stop sympy-livehtml
本地安装
如果您在本地安装了构建依赖项,则简单地使用:
cd doc
make livehtml
启动服务器。然后您的 Web 浏览器应自动打开一个新标签页,显示 SymPy 文档的索引页面。
当您完成时,可以在终端中使用ctrl-c停止服务器。
PDF 文档
注意
对于大多数贡献者而言,构建 PDF 文档并非必需。在拉取请求上,PDF 文档将在 GitHub Actions 上自动构建。每个发布版本的 PDF 文档都包含在GitHub 发布页面上。
如果在 GitHub Actions 上构建 PDF 文档失败,99%的情况是由于错误的 LaTeX 数学格式。请仔细检查您添加的任何数学公式的格式是否正确,并确保在代码中使用双反引号(单反引号将呈现为数学,而不是代码)。查看样式指南中的资源,获取有关格式化 LaTeX 数学的提示。
构建 PDF 文档需要一些额外的依赖项。首先,您需要安装包含 XeLaTeX 和 latexmk 的 TeXLive。您还需要安装 Chrome 或 Chromium,因为它用于转换某些 SVG 文件以生成 PDF。
在 Ubuntu 上,您可以使用以下命令进行安装:
apt-get install chromium-browser texlive texlive-xetex texlive-fonts-recommended texlive-latex-extra latexmk lmodern
在 Mac 上,您可以使用:
brew install texlive
brew install --cask chromium
brew tap homebrew/cask-fonts
brew install font-dejavu
在 Windows 10 上,您可以使用:
choco install chromium strawberryperl miktex dejavufonts
如果在 C:\Windows\Fonts 中未安装 DejaVu 字体,则打开 ~\AppData\Local\Microsoft\Windows\Fonts,选择所有 DejaVu 字体,右键点击并选择 为所有用户安装。
要构建 PDF 文档,请运行:
cd doc
make pdf
生成的 PDF 将位于:
_build/latex/sympy-<version>.pdf
其中 <version> 是 SymPy 的版本(例如,sympy-1.10.dev.pdf)。
依赖项
此页面列出了 SymPy 的强依赖项和可选依赖项。
当安装了几个软件包时,可以启用某些额外的 SymPy 功能。大多数用户和贡献者不需要安装下面提到的任何软件包(除了强依赖项),除非他们打算使用或贡献到 SymPy 的那些可以使用这些软件包的部分。
每个下面列出的依赖项都可以通过conda-forge安装,大多数也可以用pip安装。
此页面未列出依赖于 SymPy 本身的软件包,只列出了 SymPy 所依赖的软件包。依赖于 SymPy 的软件包的不完整列表可以在主 SymPy 网页上找到,更完整的列表可以在GitHub或libraries.io上找到。
强依赖项
SymPy 只有一个必需的强依赖项,即 mpmath,这是其工作所必需的。
-
mpmath:mpmath是一个纯 Python 的任意精度算术包。在 SymPy 计算函数的浮点值时,如使用 evalf 时,它就是底层使用的工具。
如果未安装 mpmath,SymPy 将无法运行,并且在尝试导入时会失败。如果出现类似以下错误:
ImportError: SymPy now depends on mpmath as an external library. See https://docs.sympy.org/latest/install.html#mpmath for more information.这意味着您未正确安装 mpmath。此页面说明了如何安装它。
大多数安装 SymPy 的方法,例如在安装指南中概述的方法,将自动安装 mpmath。通常只有在您没有实际安装 SymPy 时,例如在 git 仓库中直接开发 SymPy 时,才需要手动安装 mpmath。 ## 可选依赖项
这些依赖项不是使用 SymPy 所必需的。绝大多数 SymPy 函数不需要它们,但是一些函数,如绘图和自动生成代码的函数包装,则需要额外的依赖项才能正常运行。
另外,作为贡献者,在运行 SymPy 测试时,如果未安装它们所需的依赖项,则某些测试将被跳过。GitHub Actions CI,即在每个 SymPy 拉取请求上运行的操作,将在“可选依赖项”构建中自动安装这些依赖项,但如果您正在开发需要这些依赖项的 SymPy 部分,则可能需要在本地安装它们。
推荐的可选依赖项
这些依赖项不是 SymPy 运行所必需的,但建议所有用户如果可以的话都安装它们,因为它们会提高 SymPy 的整体性能。
-
gmpy2:gmpy2 是 GMP 多精度库 的 Python 封装器。它提供的整数比内置的 Python
int更快。当安装了 gmpy2 时,它会自动被某些操作整数的核心函数使用,例如 polys。更多详细信息,请参见 多项式域参考文档。SymPy 安装后会自动使用gmpy2,无需额外操作来启用它。多项式本身被 SymPy 的许多部分使用,例如积分算法、简化算法如
collect()和factor()、矩阵以及核心的一些部分。因此,安装gmpy2可以加速 SymPy 的许多部分。虽然它不是 SymPy 的必需依赖,因为它使用了非 Python 库(GMP),而且该库也不是 BSD 许可的,但我们建议所有用户都安装gmpy2,以获得更好的 SymPy 使用体验。
交互式使用
SymPy 设计用来既可以交互式使用,也可以作为库使用。当以交互式方式使用时,SymPy 能够与 IPython 和 Jupyter Notebook 进行接口。
-
IPython:如果安装了
init_session()函数和isympy命令将自动启动 IPython。除了使用 IPython 的常规好处外,这还启用了 matplotlib 的交互式绘图。还有一些标志如auto_symbols和auto_int_to_Integer只在 IPython 中有效。IPython包是运行 sympy/interactive 中一些测试所必需的。 -
Jupyter Notebook 和 Qt 控制台:在 Jupyter Notebook 中,SymPy 表达式会使用 MathJax 自动打印,在 LaTeX 的 Qt 控制台 中也是如此(如果安装了 LaTeX)。
打印
preview() 函数会自动将 SymPy 表达式转换为用 LaTeX 渲染的图像。preview() 可以将图像保存到文件中,也可以用查看器显示它。
解析
sympy.parsing 子模块中的几个函数需要外部依赖才能正常运行。请注意,目前并非所有解析器都需要外部模块。Python(parse_expr())、Mathematica(parse_mathematica())和 Maxima(parse_maxima())解析器不需要任何外部依赖。
-
antlr-python-runtime: ANTLR 可用于
LaTeX 解析器,并在 Autolev 解析器中使用。它们都需要安装 ANTLR Python 运行时。此包名为antlr-python-runtime(conda)和antlr4-python3-runtime(pip)。还需注意,ANTLR Python 运行时的版本必须与编译 LaTeX 和 Autolev 解析器时使用的版本匹配(4.10)。 -
lark: Lark 可作为
LaTeX 解析器的替代后端使用。 -
Clang Python 绑定: C 解析器(
sympy.parsing.c.parse_c)需要 Clang Python 绑定。此包名为python-clang(conda)和clang(pip)。 -
lfortran: Fortran 解析器(位于
sympy.parsing.fortran)需要 LFortran。
逻辑
函数satisfiable() 包含了 DPLL 可满足性算法的纯 Python 实现。但是如果安装了更快的 C SAT 求解器,它也可以选择使用。请注意,satisfiable() 还被ask() 使用。
-
pycosat: Pycosat 如果安装了,将会自动使用。可以通过使用
satisfiable(algorithm='pycosat')强制使用 pycosat。 -
pysat: Pysat 是一个包装多个 SAT 求解器的库。它也可以作为
satisfiable()的后端使用。目前仅实现了 Minisat,使用satisfiable(algorithm='minisat22')。
绘图
sympy.plotting.plot 模块大量使用外部绘图库来渲染图形。主要支持的绘图模块是 Matplotlib。
-
matplotlib: 大多数绘图功能需要使用 Matplotlib 绘图库。如果没有安装 Matplotlib,则大多数绘图函数将失败或产生基本的 文本绘图。
-
pyglet: SymPy 有一个子模块
sympy.plotting.pygletplot可以用于与 pyglet 模块进行 2D 和 3D 绘图接口。
lambdify
lambdify() 是一个函数,将 SymPy 表达式转换为可以使用各种库作为后端进行数值评估的函数。lambdify 是用户在 SymPy 和这些库之间进行接口操作的主要工具。它是将符号 SymPy 表达式转换为可评估数值函数的标准方法。
原则上,如果用户将适当的命名空间字典作为第三个参数传递给 lambdify,则 lambdify 可以与任何外部库进行接口。但默认情况下,lambdify 了解几个流行的数值 Python 库。这些库作为后端在 lambdify 中启用,并提供内置的转换以将 SymPy 表达式转换为这些库的适当函数。
-
NumPy: 默认情况下,如果安装了 NumPy,
lambdify使用 NumPy 创建函数(如果未安装 NumPy,则使用标准库 math 模块,尽管这主要是为了向后兼容性而提供的行为)。 -
SciPy: 如果安装了 SciPy,
lambdify将自动使用它。SciPy 在需要 lambdify 某些 特殊函数 时是必需的。 -
CuPy: CuPy 是为 CUDA GPU 提供 NumPy 兼容接口的库。
lambdify可以使用lambdify(modules='cupy')生成 CuPy 兼容的函数。 -
Jax: JAX 是一个库,使用 XLA 在 GPU 和 TPU 上编译和运行 NumPy 程序。
lambdify可以使用lambdify(modules='jax')生成 JAX 兼容的函数。 -
TensorFlow: TensorFlow 是一款流行的机器学习库。
lambdify可以使用lambdify(modules='tensorflow')来生成 TensorFlow 兼容的函数。 -
NumExpr: NumExpr 是一个快速的用于 NumPy 的数值表达式评估器。
lambdify可以使用lambdify(modules='numexpr')来生成 NumExpr 兼容的函数。 -
mpmath:
lambdify还可以生成 mpmath 兼容的函数。请注意,mpmath 已经是 SymPy 的 必需依赖项。这个功能对于将 SymPy 表达式转换为用于纯 mpmath 的函数非常有用。
代码生成
SymPy 可以通过将 SymPy 表达式转换为这些语言的有效代码来 生成代码。它还具有一些语言的功能,可以自动编译和运行代码。
注意以下依赖项 不 是 SymPy 可以生成代码的支持语言列表。相反,它是 SymPy 可以以某种方式与之进行接口的包列表。对于 SymPy 支持代码生成的大多数语言,它只是生成代表该语言代码的字符串,因此不需要该语言的依赖项来使用代码生成功能。通常,只有对于将生成的代码自动编译为可以在 Python 中使用的函数的功能才需要依赖项。lambdify() 是这种情况的一个特例,但它的依赖项在 上文 中列出。
Autowrap
-
NumPy: NumPy 和它的子包 f2py (可选)可以使用
autowrap()或ufuncify()函数生成 Python 函数。 -
Cython: Cython 可以作为
autowrap()或ufuncify()的后端。在一些sympy.codegen的测试中,Cython 也用于编译一些示例。 -
编译器:
autowrap()、ufuncify()及相关函数依赖于编译器将生成的代码编译为函数。支持大多数标准 C、C++ 和 Fortran 编译器,包括 Clang/LLVM、GCC 和 ifort。
代码打印器
大多数代码打印器生成 Python 字符串,因此不需要给定库或语言编译器作为依赖项。但是,少数代码打印器生成 Python 函数而不是字符串:
-
Aesara:
sympy.printing.aesaracode模块包含将 SymPy 表达式转换为使用Aesara(以前是 Theano)库的函数的函数。Aesara 代码生成函数返回 Aesara 图对象。 -
llvmlite:
sympy.printing.llvmjitcode模块支持从 SymPy 表达式生成 LLVM Jit。这些函数利用了llvmlite,它是围绕LLVM的 Python 封装。llvm_callable()函数生成可调用函数。 -
TensorFlow:
sympy.printing.tensorflow模块支持使用流行的机器学习库TensorFlow生成函数。与上述两个示例不同,tensorflow_code()函数确实生成 Python 字符串。但是,如果可用,将导入 TensorFlow 以自动检测 TensorFlow 版本。如果未安装,tensorflow_code()函数假定使用最新支持的 TensorFlow 版本。
仅用于测试的依赖项
-
Wurlitzer: Wurlitzer 是一个 Python 包,允许捕获 C 扩展的输出。它由
sympy.codegen子模块中的一些测试使用。它仅用于测试套件,不用于任何最终用户功能。如果未安装,某些测试将被跳过。 -
Cython: Cython 也用于部分
sympy.codegen测试用例,以编译一些示例。 -
编译器: 如果已安装,上述各种编译器将用于部分代码生成和自动包装测试。
统计
sympy.stats.sample() 函数使用外部库从给定分布生成样本。使用 sympy.stats 的抽样功能至少需要以下一个库。
-
SciPy:
sample(library='scipy')是默认选项。它使用了scipy.stats。 -
NumPy:
sample(library='numpy')使用NumPy 随机模块。 -
pymc:
sample(library='pymc')使用PyMC进行抽样。
可选的 SymEngine 后端
-
python-symengine: SymEngine 是一个快速的符号操作库,用 C++ 编写。SymEngine Python 绑定可用作 SymPy 核心的可选后端。要使用它,请首先安装 SymEngine Python 绑定(使用
pip install symengine或conda install -c conda-forge python-symengine),然后在使用 SymPy 时设置USE_SYMENGINE=1环境变量。目前,SymEngine 后端仅被 sympy.physics.mechanics 和 sympy.liealgebras 模块使用,尽管您也可以通过从
sympy.core.backend导入的方式直接与 SymPy 的 SymEngine 后端进行接口交互:>>> from sympy.core.backend import Symbol >>> # This will create a SymEngine Symbol object if the USE_SYMENGINE >>> # environment variable is configured. Otherwise it will be an ordinary >>> # SymPy Symbol object. >>> x = Symbol('x')SymEngine 后端支持仍处于实验阶段,因此在启用时某些 SymPy 函数可能无法正常工作。
Sage
Sage 是一个开源数学软件,集成了大量开源数学库。SymPy 是 Sage 使用的库之一。
大部分介于 SymPy 和 Sage 之间的代码都在 Sage 中,但是在 SymPy 中有一些 _sage_ 方法,它们负责一些非常基本的设置工作,设置 Sage/SymPy 包装器。这些方法通常只能由 Sage 自身调用。
开发依赖
在 SymPy 的典型开发中,除了 Python 和 mpmath 外不需要任何额外的依赖。
获取源代码
运行测试
基本的 SymPy 测试不需要任何额外的依赖,但是某些测试可能需要上述依赖项才能运行。当未安装可选依赖时,依赖于可选依赖的测试应该被跳过,可以通过使用 sympy.testing.pytest.skip() 函数或者将 skip = True 设置为跳过整个测试文件来实现。在测试和 SymPy 库代码中,可选模块应该使用 import_module() 导入。
-
pytest: Pytest 不是 SymPy 测试套件的必需依赖项。SymPy 有其自己的测试运行器,可以通过 SymPy 源代码目录中的
bin/test脚本或test()函数访问。然而,如果您更喜欢使用 pytest,可以使用它来运行测试,而不是 SymPy 的测试运行器。SymPy 中的测试应该使用
sympy.testing.pytest中的包装器,而不是直接使用 pytest 函数。 -
Cloudpickle: cloudpickle 包可用于比内置的 Python pickle 更有效地对 SymPy 对象进行序列化。
sympy.utilities.tests.test_pickling.py中的一些测试依赖于 cloudpickle 来运行。对于任何 SymPy 函数,它并不是必需的。 -
hypothesis: Hypothesis 是 SymPy 测试套件的必需依赖项。
构建文档
构建文档需要几个额外的依赖项。此页面详细说明了这些依赖项及其安装方法。如果你只是想查看 SymPy 的开发版本文档,可以在线查看文档的开发构建版本,地址为docs.sympy.org/dev/index.html。
运行基准测试
SymPy 的基准测试位于github.com/sympy/sympy_benchmarks。该仓库中的README文件解释了如何运行基准测试。
注意,基准测试也会自动在GitHub Actions CI上运行,因此作为贡献者通常不需要自行运行,除非你想在自己的计算机上重现基准测试结果或者向套件中添加新的基准测试。
- asv:Airspeed Velocity是用于运行基准测试的包。请注意,安装的包名为
asv。