SymPy-1-13-中文文档-三十-

SymPy 1.13 中文文档(三十)

原文:docs.sympy.org/latest/index.html

贡献

原文链接:docs.sympy.org/latest/contributing/index.html

贡献指南详细介绍了如何为 SymPy 做贡献。

如果您是 SymPy 的新贡献者,请从贡献简介开始。

内容

  • 贡献简介

  • 新贡献者指南

    • 设置开发环境

    • 开发工作流程

    • 编写测试

    • 构建文档

  • 依赖项

  • 调试

  • 文档字符串风格指南

  • 文档风格指南

  • 弃用政策

入门指南

原始文档:docs.sympy.org/latest/contributing/introduction-to-contributing.html

SymPy 是由大量贡献者创建和维护的,我们希望您也能成为其中之一!对于新贡献者来说,加入 SymPy 这样一个大型而复杂的机器可能有些令人畏惧。本页面旨在为新贡献者提供入门提示。

熟悉软件使用

我们建议您先浏览 SymPy 教程 以熟悉使用本软件,然后再开始贡献。

这个教程也有视频可供参考:

阅读论文

我们在 2017 年撰写了一篇期刊论文,概述了 SymPy 及其功能。您可以在这里阅读:

peerj.com/articles/cs-103/

浏览文档

除了教程之外,文档 中还包含大量信息。浏览不同主题可能是个好主意,以了解其他可用内容。

查看行为准则

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 的文档分布在两个地方:

  1. 文档源文件:github.com/sympy/sympy/tree/master/doc/src

  2. 源代码中函数的文档字符串: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-requestsdocs.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 

如果您喜欢使用pipvenv,您可以使用类似以下的内容

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 mergegit addgit commitgit 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 

这将运行coreutilities模块的测试。

同样地,运行质量测试:

$  ./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 是最好的选择,因为它只在本地应用。

stackoverflow.com/questions/22906851/when-would-you-use-git-info-exclude-instead-of-gitignore-to-exclude-files

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()

  • 引用任何相关的问题编号。你不需要为更改本身引用拉取请求,但应该引用修复的问题,可以用 #12345https://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 

在提交消息的底部。请参阅 docs.github.com/pull-requests/committing-changes-to-your-project/creating-and-editing-commits/creating-a-commit-with-multiple-authors

创建一个拉取请求

一旦您的更改准备好进行审查,请将它们推送到 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 有以下适用于所有拉取请求的规则:

  1. 所有新功能必须经过测试。测试应该尽可能覆盖所有可能的情况以确保正确性。这意味着不仅要最大化代码覆盖率,还要覆盖所有可能的边界情况。

  2. 在合并之前,每个拉取请求必须通过所有测试。测试会在每个拉取请求上自动运行 GitHub Actions CI。如果任何测试失败,CI 将以红色❌失败。必须在合并拉取请求之前解决这些失败。

  3. 缺陷修复应该伴随着回归测试。

编写测试的基础知识

测试位于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.raisesraises()接受异常类和 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 是否为输入 pipi/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 及其依赖项pycodestylepyflakes。有时,这些包的新版本会添加新的检查,如果你安装了旧版本,你将看不到这些检查。

./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 Trueis Falseis 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 构建成功有些棘手,因为某些依赖项如 dvipngdocbook2x 不可用。

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)。

依赖项

原文:docs.sympy.org/latest/contributing/dependencies.html

此页面列出了 SymPy 的强依赖项和可选依赖项。

当安装了几个软件包时,可以启用某些额外的 SymPy 功能。大多数用户和贡献者不需要安装下面提到的任何软件包(除了强依赖项),除非他们打算使用或贡献到 SymPy 的那些可以使用这些软件包的部分。

每个下面列出的依赖项都可以通过conda-forge安装,大多数也可以用pip安装。

此页面未列出依赖于 SymPy 本身的软件包,只列出了 SymPy 所依赖的软件包。依赖于 SymPy 的软件包的不完整列表可以在主 SymPy 网页上找到,更完整的列表可以在GitHublibraries.io上找到。

强依赖项

SymPy 只有一个必需的强依赖项,即 mpmath,这是其工作所必需的。

  • mpmathmpmath是一个纯 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 的整体性能。

  • gmpy2gmpy2GMP 多精度库 的 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_symbolsauto_int_to_Integer 只在 IPython 中有效。

    IPython 包是运行 sympy/interactive 中一些测试所必需的。

  • Jupyter Notebook 和 Qt 控制台:在 Jupyter Notebook 中,SymPy 表达式会使用 MathJax 自动打印,在 LaTeX 的 Qt 控制台 中也是如此(如果安装了 LaTeX)。

打印

preview() 函数会自动将 SymPy 表达式转换为用 LaTeX 渲染的图像。preview() 可以将图像保存到文件中,也可以用查看器显示它。

  • LaTeX:需要 TeXLiveMiKTeX 等 (\mathrm{\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: 如果安装了 SciPylambdify 将自动使用它。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/LLVMGCCifort

代码打印器

大多数代码打印器生成 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 symengineconda 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 外不需要任何额外的依赖。

获取源代码

  • git: SymPy 源代码 使用 git 版本控制系统。请参阅 安装指南 和 贡献者指南 了解如何从 git 获取 SymPy 的开发版本。

运行测试

基本的 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上运行,因此作为贡献者通常不需要自行运行,除非你想在自己的计算机上重现基准测试结果或者向套件中添加新的基准测试。

  • asvAirspeed Velocity是用于运行基准测试的包。请注意,安装的包名为asv

调试

原文:docs.sympy.org/latest/contributing/debug.html

要以调试模式启动 sympy,请设置 SYMPY_DEBUG 变量。例如在类 Unix 系统中,你可以这样做

$ SYMPY_DEBUG=True bin/isympy

或者在 Windows

设置 SYMPY_DEBUG=True > python bin/isympy

现在只需举例使用 limit() 函数。你将得到一个漂亮的打印树,对调试非常有用。

文档字符串风格指南

原文:docs.sympy.org/latest/contributing/docstring.html

通用指南

要贡献给 SymPy 的文档字符串,请完整阅读这些准则。

文档字符串(docstring)是模块、函数、类或方法定义中作为第一条语句出现的字符串文字。这样的文档字符串将成为该对象的__doc__特殊属性。

示例

这里是一个基本的文档字符串:

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)

 """ 

每个公共函数、类、方法和模块应具有描述其功能的文档字符串。对于模块中的函数或类特有的文档应位于该函数或类的文档字符串中。模块级别的文档字符串应讨论模块的目的和范围,并提供如何使用模块中的函数或类的高级示例。模块文档字符串是文件顶部的文档字符串,例如,solvers.ode 的文档字符串。

公共函数是打算由最终用户或公众使用的函数。对于公共函数,文档非常重要,因为它们将被许多人看到和使用。

另一方面,私有函数是指仅打算在 SymPy 代码中使用的函数。虽然在私有函数上撰写文档不那么重要,但在私有函数上撰写文档也有助于其他 SymPy 开发人员理解如何使用该函数。

有时不太清楚什么是公共函数,什么是私有函数。如果函数以下划线开头,则为私有函数;如果函数包含在__init__.py中,则为公共函数,但反之并非总是如此,因此有时必须根据上下文决定。总体而言,如果不确定,对函数进行文档记录总比不进行文档记录要好。

文档字符串应包含针对函数使用者的信息。特定于代码的评论或其他可能仅会分散用户注意力的注释应放在代码的注释中,而不是文档字符串中。

每个文档字符串应包含展示函数工作方式的示例。示例是文档字符串中最重要的部分。展示函数的输入和输出的单个示例可能比描述性文本段落更有帮助。

请记住,文档字符串的主要使用者是其他人类,而不是机器,因此用简单的英语描述函数的功能非常重要。同样,如何使用函数的示例应设计给人类读者,而不仅仅是为了 doctest 机制。

请记住,虽然 Sphinx 是用户消费文档字符串的主要方式,因此在编写文档字符串时(特别是对于公共函数),它是首先要考虑的平台,但并非用户消费文档字符串的唯一方式。您还可以在 IPython 中使用help()?来查看文档字符串。例如,在使用help()时,它将显示所有私有方法的文档字符串。此外,直接阅读源代码的任何人都将看到每个文档字符串。

所有公共函数、类和方法及其相应的文档字符串应导入到 Sphinx 文档中,关于这一点的说明可以在本指南末尾找到。

格式化

文档字符串是用reStructuredText格式编写的,并由Sphinx扩展。这里是关于Quick reStructuredText的简明指南。有关使用 reStructuredText 的更详细信息可以在Sphinx 文档中找到。

为了让 Sphinx 在 HTML 文档中漂亮地呈现文档字符串,编写文档字符串时应遵循一些格式化指南:

  • 始终在文档字符串周围使用“””三重双引号”””。如果在文档字符串中使用任何反斜杠,则使用 r”””原始三重双引号”””。

  • 在文档字符串的关闭引号之前包含一个空行。

  • 行不应超过 80 个字符。

  • 请始终将类级别的文档字符串写在类定义行下面,因为在源代码中更易读。

  • 如果类的各种方法很重要,则可以在文档字符串或示例中提及它们,但有关详细信息应在方法本身的文档字符串中。

  • 请注意,::创建代码块,文档字符串中很少使用。任何带有示例 Python 代码的代码示例应放在 doctest 中。始终检查由 Sphinx 渲染的最终版本在 HTML 中的显示是否正确。

  • 为了使文档字符串中的部分下划线工作得很好,使用了numpydoc Sphinx 扩展

  • 请始终仔细检查您的文档字符串格式是否正确:

  1. 确保您的文档字符串已导入到 Sphinx 中。

  2. 构建 Sphinx 文档(cd doc; make html)。

  3. 确保 Sphinx 没有输出任何错误。

  4. 打开 _build/html 中的页面,并确保格式正确。

部分

在 SymPy 的文档字符串中,建议函数、类和方法文档字符串按以下顺序包含以下部分:

  1. 单句总结

  2. 解释

  3. 示例

  4. 参数

  5. 另请参阅

  6. 参考资料

单句总结和示例部分是必需的每个文档字符串。如果不包括这些部分,文档字符串将无法通过审查。

不要更改这些支持的部分的名称,例如,“Examples”作为复数应该使用,即使只有一个示例。

SymPy 将继续支持NumPy 文档字符串指南中列出的所有部分标题。

标题应该用等号来下划线,长度相同。

如果某个部分不是必需的,并且对于所讨论的函数的信息是不必要的,请不要使用它。不必要的部分和杂乱的文档字符串会使函数更难理解。目标是提供理解该函数所需的最少信息。

1. 单句摘要

本节对每个文档字符串都是必需的。如果不包括该节,则文档字符串将无法通过审查。不需要为本节添加标题。

本节包含以句点结尾的一行简短句子,描述函数、类或方法的效果。

废弃警告应该直接放在单句摘要后面,以便立即通知用户。废弃警告应该以 Sphinx 指令中的deprecated形式书写:

.. deprecated:: 1.1

   The ``simplify_this`` function is deprecated. Use :func:`simplify`
   instead. See its documentation for more information. 

更多细节请参见文档中的废弃。

2. 解释部分

鼓励使用本节。如果选择在文档字符串中包含一个解释部分,应该用等号来下划线,长度相同地标记为“Explanation”。

Explanation
=========== 

当简短的单句摘要不够详细时,本节包含了更详细的描述,说明函数、类或方法的功能。本节应用于几句或几段来澄清功能。

3. 示例部分

本节对每个文档字符串都是必需的。如果不包括该节,则文档字符串将无法通过审查。应该用等号来下划线,长度相同地标记为“Examples”(即使只有一个示例)。

Examples
======== 

本节包含展示函数如何工作的示例,称为 doctests。Doctests 应该足够复杂,以完全展示函数的 API 和功能,但也足够简单,使用户可以轻松理解。完美的 doctest 确切地告诉用户关于函数的一切,而不需要阅读文档字符串的其他部分。

在 doctest 之前应始终有一个空行。提供多个示例时,它们应该用空行分隔开。解释示例的注释应该在其上下都有空行。

不要将 doctest 视为测试。将它们视为恰好被测试的示例。它们应该展示函数的 API 给用户(即输入参数的样子,输出的样子,以及它的作用)。如果只想测试某些内容,请将测试添加到相关的test_*.py文件中。

你可以使用./bin/coverage_doctest.py脚本来测试文件或模块的 doctest 覆盖率。使用./bin/doctest来运行 doctest。

只有在不可能测试示例时,才应跳过其测试。如果必要,可以通过添加特殊注释来跳过示例的测试。

示例

>>> import random
>>> random.random()      
0.6868680200532414 

如果示例超过 80 个字符,则应进行换行。示例应该被换行,以便它们仍然是有效的 Python 代码,使用 ... 作为 Python 提示中的继续符号。例如,来自 ODE 模块文档:

示例

>>> 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) 

这里 dsolve(cos(f(x)) - (x*sin(f(x)) - f(x)**2)*f(x).diff(x), f(x), hint='1st_exact') 太长了,因此我们在逗号后进行换行以便其可读性,并在连续行上放置 ...。如果这样做不正确,doctest 将失败。

命令的输出也可以换行。在这种情况下,不应使用 ...。doctester 自动接受换行的输出。

示例

>>> list(range(30))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, 26, 27, 28, 29] 

在 doctest 中,像 sympy import ... 这样写入导入,而不是 import sympyfrom sympy import *。要定义符号,请使用 from sympy.abc import x,除非名称不在 sympy.abc 中(例如,如果它具有假设),在这种情况下,使用 symbols,如 x, y = symbols('x y')

通常应运行 ./bin/doctest 来确保您的示例正确运行,并在不正确时进行修复。

4. 参数部分

鼓励包含此部分。如果选择在文档字符串中包含参数部分,则应以等号长度下划线为标签“参数”。

Parameters
========== 

如果在函数、类或方法名称后括号中列出参数,则必须包含一个参数部分。

此部分包含函数参数、关键字及其各自类型的描述。

将变量用双反引号括起来。冒号前必须有一个空格,或者如果类型不存在则可以省略冒号。对于参数类型,尽可能精确。如果不需要指定关键字参数,请使用 optional。可选关键字参数具有默认值,这些默认值显示为函数签名的一部分。它们也可以在描述中详细说明。

当参数只能假定一组固定值中的一个时,这些值可以用大括号列出,其中默认值首先出现。当两个或更多输入参数具有完全相同的类型、形状和描述时,它们可以合并。

如果参数部分格式不正确,则文档构建将呈现不正确。

如果希望包含返回部分,请将其编写为具有自己标题的单独部分。

示例

这是一个正确格式化的参数部分示例:

def opt_cse(exprs, order='canonical'):
  """
 Find optimization opportunities in Adds, Muls, Pows and negative
 coefficient Muls.

 Parameters
 ==========

 exprs : list of sympy expressions
 The expressions to optimize.
 order : string, 'none' or 'canonical'
 The order by which Mul and Add arguments are processed. For large
 expressions where speed is a concern, use the setting order='none'.

 """ 

5. 参见部分

鼓励包含此部分。如果选择在文档字符串中包含参见部分,则应以等号长度下划线为标签“参见”。

See Also
======== 

此部分包含相关函数、类和方法的列表。如果需要,相关项可以用简洁的片段描述(不需要完整句子)。如果描述跨越多行,则必须缩进后续行。

“参见”部分应仅用于引用其他 SymPy 对象。任何链接都应嵌入到文本的文档字符串中,详见参考文献部分。

不要引用 class:Classnameclass:`Classname`:class:`Classname` 类型,而只引用它们的类名。

示例

这是一个带有简短描述的正确格式的“参见”部分示例:

class erf(Function):
  r"""
 The Gauss error function.

 See Also
 ========

 erfc: Complementary error function.
 erfi: Imaginary error function.
 erf2: Two-argument error function.
 erfinv: Inverse error function.
 erfcinv: Inverse Complementary error function.
 erf2inv: Inverse two-argument error function.

 """ 

这是一个只包含名称列表的正确格式的“参见”部分示例:

class besselj(BesselBase):
  r"""
 Bessel function of the first kind.

 See Also
 ========

 bessely, besseli, besselk

 """ 

6. 参考文献部分

鼓励包含此部分。如果您选择在文档字符串中包含一个参考文献部分,它应该用标题“参考文献”标记,并在下方用等号长度划线。

References
========== 

此部分由在前面各节中引用的参考文献列表组成。任何对其他 SymPy 对象的引用应放在“参见”部分。

参考文献部分应包括在线资源、论文引用和/或任何其他提供有关函数一般信息的印刷资源。参考文献旨在增强文档字符串,但不应要求理解它。参考文献按引用顺序编号,从一开始。

对于在线资源,只链接到免费且稳定的在线资源,如维基百科、Wolfram MathWorld 和 NIST 数学函数数字图书馆(DLMF),这些资源不太可能出现超链接失效。

论文的参考文献应按照以下顺序包括:引用编号、作者姓名、作品标题、期刊或出版物、出版年份、页码。

如果有 DOI(数字对象标识符),请在引用中包含并确保它是可点击的超链接。

示例

这是引用了印刷资源的参考文献部分示例:

References
==========

.. [1] [Kozen89] D. Kozen, S. Landau, Polynomial Decomposition Algorithms,
       Journal of Symbolic Computation 7 (1989), pp. 445-456 

这是引用印刷和在线资源的参考文献部分示例:

References
==========

.. [1] Abramowitz, Milton; Stegun, Irene A., "Chapter 9," Handbook of
       Mathematical Functions with Formulas, Graphs, and Mathematical
       Tables, eds. (1965)
.. [2] Luke, Y. L., The Special Functions and Their Approximations,
       Volume 1, (1969)
.. [3] https://en.wikipedia.org/wiki/Bessel_function
.. [4] https://functions.wolfram.com/Bessel-TypeFunctions/BesselJ/ 

样本文档字符串

这是一个正确格式的文档字符串示例:

class gamma(Function):
  r"""
 The gamma function

 .. math::
 \Gamma(x) := \int^{\infty}_{0} t^{x-1} e^{-t} \mathrm{d}t.

 Explanation
 ===========

 The ``gamma`` function implements the function which passes through the
 values of the factorial function (i.e., $\Gamma(n) = (n - 1)!$), when n
 is an integer. More generally, $\Gamma(z)$ is defined in the whole
 complex plane except at the negative integers where there are simple
 poles.

 Examples
 ========

 >>> from sympy import S, I, pi, oo, gamma
 >>> from sympy.abc import x

 Several special values are known:

 >>> gamma(1)
 1
 >>> gamma(4)
 6
 >>> gamma(S(3)/2)
 sqrt(pi)/2

 The ``gamma`` function obeys the mirror symmetry:

 >>> from sympy import conjugate
 >>> conjugate(gamma(x))
 gamma(conjugate(x))

 Differentiation with respect to $x$ is supported:

 >>> from sympy import diff
 >>> diff(gamma(x), x)
 gamma(x)*polygamma(0, x)

 Series expansion is also supported:

 >>> from sympy import series
 >>> series(gamma(x), x, 0, 3)
 1/x - EulerGamma + x*(EulerGamma**2/2 + pi**2/12) +
 x**2*(-EulerGamma*pi**2/12 - zeta(3)/3 - EulerGamma**3/6) + O(x**3)

 We can numerically evaluate the ``gamma`` function to arbitrary
 precision on the whole complex plane:

 >>> gamma(pi).evalf(40)
 2.288037795340032417959588909060233922890
 >>> gamma(1+I).evalf(20)
 0.49801566811835604271 - 0.15494982830181068512*I

 See Also
 ========

 lowergamma: Lower incomplete gamma function.
 uppergamma: Upper incomplete gamma function.
 polygamma: Polygamma function.
 loggamma: Log Gamma function.
 digamma: Digamma function.
 trigamma: Trigamma function.
 beta: Euler Beta function.

 References
 ==========

 .. [1] https://en.wikipedia.org/wiki/Gamma_function
 .. [2] https://dlmf.nist.gov/5
 .. [3] https://mathworld.wolfram.com/GammaFunction.html
 .. [4] https://functions.wolfram.com/GammaBetaErf/Gamma/

 """ 

数学函数类的文档字符串

SymPy 不同寻常之处在于它还有作为数学函数的类。数学函数类的文档字符串应包括特定于此类别的详细信息,如下所述:

  • 解释部分应包括函数的数学定义。这应该使用 LaTeX 数学公式。对于内联数学,使用 $$,对于主要定义,使用 .. math:: 来显示数学公式。公式中的变量名应与参数名匹配,并且 LaTeX 格式应与 SymPy 使用的 LaTeX 漂亮打印格式匹配。在适当的情况下,数学定义应说明其定义域,特别是如果定义域与复数不同。

  • 如果文献中对一个函数有多种约定,请确保清楚指明 SymPy 使用的是哪种约定。

  • 解释部分还可以包括有关函数的一些重要数学事实。这些也可以在示例部分中进行演示。数学讨论不应太长,因为用户可以查阅参考资料获取更多细节。

  • 文档字符串不需要讨论每个实现细节,例如在“eval”方法中定义了哪些操作或在哪些点上进行评估。这些重要或有启发性的实例可以在示例部分中展示。

  • 文档字符串应该放在类级别(紧挨着包含“class”一行的位置)。“eval” 方法不应有文档字符串。

  • 类的私有方法,即以下划线开头的任何方法,不需要进行文档化。如果你愿意,它们仍然可以被文档化,但请注意这些文档字符串不会被导入 Sphinx 文档中,因此只有阅读代码的开发人员才能看到。因此,如果有任何非常重要的内容要提及,应该放在类级别的文档字符串中。

编写文档字符串的最佳实践

在编写文档字符串时,请遵循与编写叙述文档时相同的格式、风格和语气偏好。有关指南,请参阅编写文档的最佳实践,格式、风格和语气。

将文档字符串导入 Sphinx 文档

下面是从doc/src/modules/geometry目录中导入几段相关文档字符串到文档中的摘录:

Utils
=====

.. module:: sympy.geometry.util

.. autofunction:: intersection

.. autofunction:: convex_hull

.. autofunction:: are_similar

Points
======

.. module:: sympy.geometry.point

.. autoclass:: Point
   :members:

Lines
=====

.. module:: sympy.geometry.line

.. autoclass:: LinearEntity
   :members:

.. autoclass:: Line
   :members:

.. autoclass:: Ray
   :members:

.. autoclass:: Segment
   :members:

Curves
======

.. module:: sympy.geometry.curve

.. autoclass:: Curve
   :members:

Ellipses
========

.. module:: sympy.geometry.ellipse

.. autoclass:: Ellipse
   :members:

.. autoclass:: Circle
   :members:

Polygons
========

.. module:: sympy.geometry.polygon

.. autoclass:: Polygon
  :members:

.. autoclass:: RegularPolygon
   :members:

.. autoclass:: Triangle
   :members: 

第一个命名空间设置为特定子模块(文件)使用 .. module:: 指令,然后使用 .. autoclass::.. autofunction:: 相对于该子模块(文件)导入文档字符串。其他方法要么使用所有对象的完整路径太麻烦,要么会破坏某些功能(使用 .. module:: sympy.geometry 相对于主模块导入破坏了 viewcode Sphinx 扩展)。doc/src/modules/ 中的所有文件应该使用这种格式。

交叉引用

任何引用另一个 SymPy 函数的文本都应该格式化,以便自动创建对该函数文档的交叉引用链接。这是使用 RST 交叉引用语法完成的。这里有两种不同的对象在这里有惯例:

1. 被包含在 from sympy import * 中的对象,例如,sympy.acos

对于这些情况,使用 :obj:`~.acos()`~ 让渲染后的 HTML 中只显示 acos 而不是完全限定名称 sympy.functions.elementary.trigonometric.acos。(这鼓励从全局 sympy 命名空间导入名称而不是特定的子模块。). 使函数名被自动找到。(如果 Sphinx 给出多个名称找到的警告,请用完整名称替换 .。例如,:obj:`~sympy.solvers.solvers.solve()`。)添加一对括号表示这个名字是一个函数、方法或类的约定。

你也可以使用更具体的类型指示符,而不是 obj(参见 www.sphinx-doc.org/en/master/usage/restructuredtext/domains.html#cross-referencing-python-objects)。然而,obj 总是有效的,有时候 SymPy 的名称并不是你期望的类型。例如,数学函数对象如 sin 实际上不是 Python 函数,而是 Python 类,因此 :func:`~.sin` 将不起作用。

2. 不包含在 from sympy import * 中的对象,例如,sympy.physics.vector.dynamicsymbols

这可以是不包括在主 sympy/__init__.py 中的子模块中的公共 API 对象,例如物理子模块,或者不一定要由最终用户使用的私有 API 对象(但仍然需要文档化)。在这种情况下,你必须显示完全限定名称,因此不要使用 ~. 语法。例如,:obj:`sympy.physics.vector.dynamicsymbols()`

你也可以编写自定义文本,链接到某些东西的文档使用以下语法 :obj:`custom text<object>`。例如,:obj:`正弦函数 <.sin>` 会产生文本“正弦函数”,链接到 sin 的文档。请注意,这里不应该使用 ~ 字符。

注意在文档字符串的 另请参见 部分中的引用不需要 :obj: 语法。

如果生成的交叉引用写错了,Sphinx 在构建文档时会出现错误,例如:

WARNING: py:obj reference target not found: expand 

这里有一些故障排除的提示来修复错误:

  • 确保你已经按照上述描述使用了正确的语法。

  • 确保你拼写了函数名正确。

  • 检查您试图交叉引用的函数是否确实包含在 Sphinx 文档中。如果没有,Sphinx 将无法为其创建引用。在这种情况下,您应将其添加到相应的 RST 文件中,如 Docstring Guidelines 中所述。

  • 如果函数或对象未包含在from sympy import *中,则需要使用完全限定名称,例如sympy.submodule.submodule.function而不是仅仅function

  • 完全限定名称必须包括功能直至文件的完整子模块。例如,sympy.physics.vector.ReferenceFrame将不起作用(即使您可以在代码中以这种方式访问它)。它必须是sympy.physics.vector.frame.ReferenceFrame

  • 如果您要引用的内容实际上没有可以链接的地方,请勿使用:obj:语法。而是使用双反引号将其标记为代码。不能链接到的示例包括 Python 内置函数如intNotImplementedError,SymPy 外的其他模块的函数如matplotlib.plot,以及特定于手头文本的变量或参数名称。一般来说,如果无法访问对象作为sympy.something.something.object,则无法交叉引用,不应使用:obj:语法。

  • 如果您正在使用类型特定标识符之一,比如:func:,请确保其类型正确。:func:仅适用于 Python 函数。对于类,请使用:class:,对于类的方法,请使用:method:。一般来说,建议使用:obj:,因为这适用于任何类型的对象。

  • 如果无法使交叉引用语法工作,请继续提交原样拉取请求,并请求审阅者帮助。

您可能还会看到类似以下的错误:

WARNING: more than one target found for cross-reference 'subs()':
sympy.core.basic.Basic.subs, sympy.matrices.matrixbase.MatrixBase.subs,
sympy.physics.vector.vector.Vector.subs,
sympy.physics.vector.dyadic.Dyadic.subs 

例如,使用:obj:`~.subs` 。这意味着.不足以找到函数,因为 SymPy 中有多个名为subs的名称。在这种情况下,您需要使用完全限定名称。您仍然可以使用~来在最终文本中缩短它,例如:obj:`~sympy.core.basic.Basic.subs`

Python 文件中警告的行号是相对于 docstring 顶部而不是文件本身的。行号通常不完全正确,因此您通常需要搜索 docstring 以找到警告所指的部分。这是由于 Sphinx 中的一个 bug。

文档风格指南

原文链接:docs.sympy.org/latest/contributing/documentation-style-guide.html

一般指南

文档是开源项目中最受重视的方面之一。文档教会用户和贡献者如何使用项目,如何贡献以及开源社区内的行为规范。但根据 GitHub 的开源调查,不完整或令人困惑的文档是开源项目中最常见的问题。本风格指南旨在改变这一现状。

本风格指南的目的是为 SymPy 社区提供一套在编写 SymPy 文档时可以利用和遵循的风格和格式指南。遵循本风格指南提供的准则将为 SymPy 的文档带来更大的一致性和清晰度,支持其成为一个功能齐全的开源计算代数系统(CAS)的使命。

SymPy 文档位于docs.sympy.org,由源代码中的文档字符串和专用叙述文档文件在doc/src 目录中生成。两者均采用Sphinx扩展的reStructuredText格式。

文档存储在doc/src 目录中,以及嵌入在 Python 源代码中的文档字符串都由 Sphinx 及其各种扩展处理。这意味着文档源格式由文档处理工具指定。SymPy 文档风格指南提供了编写 SymPy 文档的基本要素以及我们相对于这些文档处理工具指定的任何风格偏差。以下列出了处理工具:

所有上述处理工具支持的功能都可以在 SymPy 文档中使用,但本样式指南将覆盖上述任何建议。请注意,我们不遵循 PEP 257 或 www.python.org 的文档建议。

如果您是第一次为 SymPy 做贡献,请阅读我们的贡献简介页面以及本指南。

文档类型

SymPy 文档的主要位置有四个:

SymPy 网站 www.sympy.org/

SymPy 网站的主要功能是向用户和开发人员宣传软件。它还作为指向网络上其他相关资源的初始位置。SymPy 网站提供有关 SymPy 及其获取方式的基本信息,以及用于向用户宣传的示例,但没有技术文档。源文件位于 SymPy 网页目录。适用于网站的内容包括:

  • SymPy 和 SymPy 社区的一般描述

  • 主要软件功能的解释/演示

  • 列出了使用 SymPy 的其他主要软件

  • 用户入门信息(下载和安装说明)

  • 开发者入门信息

  • 用户可以获取 SymPy 使用帮助和支持的地方

  • SymPy 的最新消息

SymPy 文档 docs.sympy.org

这是用户学习如何使用 SymPy 的主要位置。它包含了 SymPy 的教程以及所有模块的技术文档。源文件托管在主 SymPy 仓库的doc 目录,使用Sphinx 站点生成器构建,并自动上传到 docs.sympy.org 网站。从 docs 目录中不同的源文件生成两种主要类型的页面:

  • 叙述页面:reStructuredText 文件,对应手动编写的文档页面。例如,教程 RST 文件。一般来说,如果您的文档不是 API 文档,它应该属于叙述页面。

  • API 文档页面:reStructuredText 文件,包含生成应用程序接口文档的指令。这些文档是从 SymPy Python 源代码自动生成的。

SymPy 源代码 github.com/sympy/sympy

大多数函数和类都包含作为 docstring 形式的内部文档,其中解释了函数并包含称为 doctest 的示例。这些 docstring 的目的是解释该类或函数的 API。这些 doctest 示例作为测试套件的一部分进行测试,以确保它们始终产生其所说的输出。这里是一个示例 docstring。大多数 docstring 也会自动包含在上述 Sphinx 文档中,以便它们出现在 SymPy 文档网站上。这是 SymPy 网站上相同的相同 docstring。这些 docstring 采用特定的格式,以便 Sphinx 能够正确渲染它们用于文档网站。SymPy 源码中所有的技术文档都以源代码注释的形式存在,尽管这通常不构成实质性内容,也不会显示在文档网站上。

SymPy Wiki github.com/sympy/sympy/wiki

SymPy Wiki 可以由任何人在无需审核的情况下进行编辑。其中包含各种类型的文档,包括:

叙述文档指南

全面的文档,或者非围绕 API 参考的文档,应作为 Sphinx 文档中的叙述性文档撰写(位于doc/src 目录)。叙述文档不驻留在 Python 源文件中,而是作为独立的 restructured 文件存在于 doc/src 目录中。SymPy 的叙述性文档定义为教用户如何使用 SymPy 的集体文档、教程和指南。参考文档应放在文档字符串中,并通过 autodoc 拉入 RST。RST 本身应只包含不是单个特定函数参考的叙述式文档。

使用 Markdown 撰写文档

叙述性文档可以使用 Restructured Text(.rst)或 Markdown(.md)编写。Markdown 文档使用MyST。有关如何在 Markdown 中撰写文档的更多信息,请参阅此指南。Markdown 仅支持叙述性文档。文档字符串应继续使用 RST 语法。本样式指南中不特定于 RST 语法的任何部分仍然适用于 Markdown 文档。

编写文档的最佳实践

撰写文档时,请遵循这些格式化、样式和语调偏好。

格式首选项

为了使 SymPy 网站上的数学和代码正确渲染,请遵循这些格式化准则。

数学

由美元符号 $ _ $ 包围的文本将被渲染为 LaTeX 数学公式。任何应作为 LaTeX 数学公式显示的文本都应写为 $math$。在文档的 HTML 版本中,MathJax 将渲染这些数学公式。

示例

The Bessel $J$ function of order $\nu$ is defined to be the function
satisfying Bessel’s differential equation. 
```  #### LaTeX 推荐

+   如果文档字符串包含任何 LaTeX 代码,请确保将其设置为“原始”状态。有关详细信息,请参见文档字符串格式化部分。

+   如果不确定如何渲染某些内容,可以使用 SymPy `latex()` 函数。但请确保删除不重要的部分(如下面的项目符号)。

+   避免不必要的 `\left` 和 `\right`(但在必要时确保使用它们)。

+   避免不必要的 `{}`。(例如,写 `x²` 而不是 `x^{2}`。)

+   使用空格使方程最易于阅读。

+   始终检查最终呈现效果,确保符合预期。

+   HTML 文档生成不会因为存在无效的数学而失败,而是会在页面上显示为错误。但是,在 GitHub Actions 上拉取请求时运行的 LaTeX PDF 构建将失败。如果 CI 中的 LaTeX PDF 构建失败,则可能存在 LaTeX 数学的问题。

**示例**

正确:

```py
\int \sin(x)\,dx 

不正确:

\int \sin{\left( x\right)}\, dx 

要了解如何在 LaTeX 中编写数学更深入的资源,请参见:

代码

应该原样打印的文本,例如代码,应该用一对双反引号 like this 包围起来。

示例

To use this class, define the ``_rewrite()`` and ``_expand()`` methods. 

有时一个变量在数学和代码中是相同的,并且甚至可以出现在同一段落中,这使得很难知道它应该格式化为数学还是代码。如果所讨论的句子涉及数学,则应使用 LaTeX,但如果句子讨论的是 SymPy 实现,则应使用代码。

一般来说,可以根据所讨论的变量在代码和数学中是否以不同方式呈现来判断。例如,希腊字母 α 在代码中写作 alpha,在 LaTeX 中写作 $\alpha$。原因是 $\alpha$ 不能在涉及 Python 代码的上下文中使用,反之 alpha 在数学上下文中也是不正确的,因为它不能显示为希腊字母 (α)。

示例

class loggamma(Function):
  r"""
 The ``loggamma`` function implements the logarithm of the gamma
 function (i.e, $\log\Gamma(x)$).

 """ 

在函数名称后列出的参数中,书面文本中应使用斜体,使用 Sphinx 强调,像 *this*

示例

def stirling(n, k, d=None, kind=2, signed=False):
  """
 ...

 The first kind of Stirling number counts the number of permutations of
 *n* distinct items that have *k* cycles; the second kind counts the
 ways in which *n* distinct items can be partitioned into *k* parts.
 If *d* is given, the "reduced Stirling number of the second kind" is
 returned: $S^{d}(n, k) = S(n - d + 1, k - d + 1)$ with $n \ge k \ge d$.
 This counts the ways to partition $n$ consecutive integers into $k$
 groups with no pairwise difference less than $d$.

 """ 

请注意,在上述示例中,nk 的第一个实例是指 stirling 函数的输入参数。因为它们是 Python 变量,但也是单独列出的参数,所以它们被格式化为斜体参数。(n) 和 (k) 的最后一个实例讨论的是数学表达式,因此它们被格式化为数学。

如果一个变量是代码,但也是单独写的参数,参数格式应优先,并且应该用斜体显示。然而,如果一个参数出现在一个较大的代码表达式中,则应在双反引号内作为代码呈现。如果一个变量只是代码而不是参数,则应在双反引号内作为代码呈现。

请注意,与 SymPy 中的参数或代码不同,对 SymPy 中其他函数的引用处理方式不同。如果某些内容引用了 SymPy 中的另一个函数,则应使用交叉引用 reStructuredText 语法。有关更多信息,请参阅交叉引用部分。

标题

在 reStructuredText 文件中,通过使用至少与文本一样长的标点符号在标题下方(可选地上方)创建章节标题。

通常情况下,某些字符不分配标题级别,因为结构是从标题的连续性中确定的。但是,对于 SymPy 的文档,这里建议的惯例是:

=== 与上划线:标题(顶级标题)

=== 标题 1

--- 标题 2

^^^ 标题 3

~~~ 标题 4

""" 标题 5

样式偏好

拼写和标点

SymPy 所有叙述性写作均遵循美国拼写和标点符号标准。例如,“color”优先于“colour”,逗号应放在引号内。

示例

If the ``line_color`` aesthetic is a function of arity 1, then the coloring
is a function of the x value of a point.

The term "unrestricted necklace," or "bracelet," is used to imply an object
that can be turned over or a sequence that can be reversed. 

如果存在关于单词拼写的歧义,例如以人名命名的函数,应参考实际 SymPy 函数的拼写。

例如,切比雪夫多项式以帕夫努蒂·利沃维奇·切比雪夫命名,其名称有时从俄语转写为以“T”拼写,但在 SymPy 中应始终拼写为“Chebyshev”以指代 SymPy 函数。

示例

class chebyshevt(OrthogonalPolynomial):
  r"""
 Chebyshev polynomial of the first kind, $T_n(x)$
 ...

 """ 

大写格式

在所有 SymPy 标题中首选使用大写标题格式。

示例

What is Symbolic Computation?
----------------------------- 

语调偏好

在 SymPy 所有文档中,请使用以下格式:

  • 现在时态(例如,在接下来的部分,我们将学习…)

  • 第一人称包含复数(例如,我们以长方式完成了此操作,但现在可以尝试以短方式进行…)

  • 使用通用代词“you”而不是“one”。或者使用“读者”或“用户”。(例如,您可以通过以下方法访问此功能… 用户然后可以通过以下方式访问此功能…)

  • 使用性别中立代词“they”而不是“he”或“she”。(例如,一个好的文档字符串告诉用户他们需要知道的一切。)

避免使用“显然”,“容易”,“简单”,“只是”或“直接”等多余或轻视的词语。

避免使用不友好或基于评判的短语,如“那是错误的”。而是使用友好和包容性语言,如“一个常见的错误是…”

避免多余的短语,如“我们只需要再做一件事。”

弃用政策

原文:docs.sympy.org/latest/contributing/deprecations.html

此页面概述了 SymPy 在执行弃用时的政策,并描述了开发人员应采取的适当步骤。

SymPy 中所有当前活动弃用的列表可以在当前活动弃用列表中找到。

什么是弃用?

弃用是以允许用户更新其代码的方式进行不向后兼容的更改。已弃用的代码仍然像以前一样工作,但每当有人使用它时,屏幕上会打印一条警告,指示将来版本中将删除 SymPy 的内容,并指示用户应使用的替代方案。

这使得用户有机会更新他们的代码而不会完全中断。这还使得 SymPy 有机会向用户提供关于如何更新其代码的信息性消息,而不是使他们的代码简单地报错或开始提供错误答案。

首先尽量避免不向后兼容的更改

不轻易进行不向后兼容的 API 更改。任何向后兼容性断裂都意味着用户需要修复他们的代码。每当你想进行破坏性更改时,都应考虑这是否值得用户付出这样的痛苦。每次 SymPy 发布新版本时,用户都必须更新他们的代码以匹配新的 API,这会让他们对该库感到沮丧,并可能寻找更稳定的替代方案。请考虑您想要的行为是否可以以与现有 API 兼容的方式完成。新的 API 并不一定需要完全取代旧的 API。有时旧的 API 可以与更新、设计更好的 API 并存而不被移除。例如,更新后的 solveset API 旨在作为旧的 solve API 的优秀替代品,但旧的solve()函数仍然完整且仍然受支持。

当添加新功能时,尝试在意 API 设计是很重要的。试着考虑一个函数未来可能做什么,并设计 API 以便它可以在不进行破坏性更改的情况下实现。例如,如果您向对象A.attr添加属性,那么以后将无法将该属性转换为方法A.attr()以便它可以接受参数,除非以不向后兼容的方式进行。如果您对新功能的 API 设计不确定,一种选择是将新功能标记为显式私有或实验性。

话虽如此,可能决定必须以某种不兼容的方式更改 SymPy 的 API。更改 API 的原因可能包括:

  • 现有的 API 令人困惑。

  • API 中存在不必要的冗余。

  • 现有的 API 限制了可能性。

因为 SymPy 的核心用例之一是作为库可用,我们非常严肃地对待 API 的破坏性变更。每当需要 API 破坏性变更时,应采取以下步骤:

  • 与社区讨论 API 更改。确保改进的 API 确实更好,并且值得破坏。正确地设置 API 非常重要,这样我们就不需要再次破坏 API 来“修复”它。

  • 如果可能的话,废弃旧的 API。如何进行技术步骤的描述见下文。

  • 记录更改,以便用户知道如何更新他们的代码。所需添加的文档描述见下文。

何时需要废弃一个改变?

在考虑一个改变是否需要废弃时,必须考虑两件事:

  • 更改是否向后不兼容?

  • 行为是否在改变公共 API?

如果用户代码在更改后无法正常工作,则该更改是向后不兼容的。

什么算是“公共 API”需要具体情况具体分析。关于 SymPy 中什么构成和不构成公共 API 的确切规则尚未完全编码。清理公共和私有 API 之间的区别,以及参考文档中的分类,目前仍然是 SymPy 的一个开放问题

这里有些东西构成了公共 API。注意:这些只是一般指导方针。这个列表并非详尽无遗,总有例外情况。

公共 API

  • 函数名。

  • 关键字参数名称。

  • 关键字参数默认值。

  • 位置参数顺序。

  • 子模块名称。

  • 定义函数所使用的数学约定。

还有一些通常不是公共 API 的东西,因此不需要废弃以进行更改(再次强调,这个列表仅是一般指导方针)。

非公共 API

  • 表达式的精确形式。一般来说,函数可能被更改为返回同一表达式的不同但数学上等价的形式。这包括函数返回以前无法计算的值。

  • 私有的函数和方法,即仅用于内部。这些东西通常应该以下划线 _ 前缀,尽管这种约定目前在 SymPy 代码库中并不普遍遵循。

  • 任何明确标记为“实验性”的东西。

  • 先前数学上不正确的行为更改(一般而言,修复错误不被视为破坏性更改,因为尽管有这种说法,但 SymPy 中的错误不是特性)。

  • 在最近的发布之前添加的任何内容。尚未发布的代码不需要被废弃。如果要更改新代码的 API,最好在发布之前进行,以便未来的发布不需要废弃。

注意:参考文档包括参考文档中的公共和私有 API 函数,许多应包括在内的函数未包括在其中,或者根本没有文档化,因此这不应用于确定某些内容是公共的还是私有的。

如果不确定,即使可能实际上并非“公共 API”,也没有害处废弃某些内容。

废弃的目的

废弃有几个目的:

  • 允许现有代码继续工作一段时间,让人们有机会升级 SymPy,而不立即修复所有废弃问题。

  • 警告用户其代码将来会在某个版本中断。

  • 告知用户如何修复其代码,以使其在未来版本中继续工作。

所有废弃警告应该是用户可以通过更新其代码来移除的。应避免无条件触发废弃警告,即使使用了“正确”的新 API。

这也意味着所有废弃的代码必须有一个完全可用的替代方案。如果没有办法让用户更新其代码,那么这意味着相关 API 尚未准备好废弃。废弃警告应通知用户如何更改其代码,以使其在同一版本的 SymPy 中继续工作,以及所有未来版本,如果可能的话,还包括之前的版本。参见下文。

废弃始终应该

  1. 允许用户在废弃期间继续使用现有的 API(附有警告,可以通过warnings.filterwarnings进行消除)。

  2. 允许用户始终修复其代码,以防止出现警告。

  3. 用户修复其代码后,废弃的代码移除后应继续工作。

第三点很重要。我们不希望“新”方法在废弃期结束时本身导致另一个 API 断裂。这样做将完全抵消废弃的目的。

当技术上不可能废弃时

在某些情况下,技术上不可能进行符合上述三条规则的废弃。此类性质的 API 更改应该被认为是最重要的,因为它们将立即破坏用户的代码而不发出警告。还应考虑到用户支持多个 SymPy 版本的容易程度,一个带有更改,一个不带有更改。

如果决定更改仍然值得,有两种选择:

  • 立即进行不可废弃的更改,不发出警告。这将打破用户代码。

  • 警告代码将来会更改。在有版本引入破坏性更改之前,用户将无法修复其代码,但他们至少会意识到即将进行的更改。

应该根据具体情况决定采取哪种方式。

废弃应该持续多久?

在首次主要发布时,弃用应至少持续 1 年。这只是最短期限:弃用可以保持更长时间。如果某项变更对用户的迁移特别困难,则应延长弃用期限。对于不会对维护造成重大负担的已弃用功能,期限也可以延长。

弃用期限策略基于时间而非发布。原因有几点:首先,SymPy 没有定期的发布计划。有时一年内可能发布多个版本,有时可能只有一个版本。基于时间的策略确保用户有充足的机会更新其代码,无论发布频率如何。

其次,SymPy 不采用严格的语义化版本方案。SymPy 的 API 表面和贡献数量都足够大,几乎每个主要版本都会在某些子模块中进行一些弃用和不向后兼容的更改。将这些编码到版本号中几乎是不可能的。开发团队也不会在极端情况下向之前的主要版本回溯更改。因此,基于时间的弃用方案比基于版本的方案更符合 SymPy 的发布模型。

最后,基于时间的方案消除了通过提前发布来“篡改”弃用期限的任何诱惑。开发人员加速移除弃用功能的最佳方法是尽早发布包含弃用的版本。

如何弃用代码

检查清单

这里是进行弃用的检查清单。详细信息请参见下文。

  • 与社区讨论不向后兼容的更改。根据上述讨论确保更改真的值得。

  • 从代码库中的所有地方(包括 doctest 示例)删除所有弃用代码的实例。

  • 在代码中添加sympy_deprecation_warning()

    • sympy_deprecation_warning()编写描述性消息。确保消息解释了何为弃用以及替换方式。消息可以是多行字符串并包含示例。

    • deprecated_since_version设置为sympy/release.py中的版本(不含.dev)。

    • active_deprecations_target设置为active-deprecations.md文件中使用的目标。

    • 确保stacklevel设置为正确的值,以便弃用警告显示用户的代码行。

    • 确认控制台中的弃用警告显示效果良好。

  • 在相关文档字符串的顶部添加一个.. deprecated:: <version>的注释。

  • doc/src/explanation/active-deprecations.md文件中添加一个部分。

    • 在节标题之前添加交叉引用目标(deprecation-xyz)=(与上述的active_deprecations_target使用的引用相同)。

    • 解释什么是已弃用以及应该替换的内容。

    • 解释为什么给定的事物已弃用。

  • 添加一个使用warns_deprecated_sympy()的测试,测试已弃用警告是否正确发出。此测试应该是代码中唯一使用已弃用功能的地方。

  • 运行测试套件以确保上述测试正常工作,并且没有其他代码使用了已弃用的代码,否则测试将失败。

  • 在您的 PR 中,为弃用添加一个BREAKING CHANGE条目到发布说明中。

  • 一旦合并了 PR,请手动将更改添加到维基上的“向后兼容性断裂和弃用”部分的发布说明中。

将弃用添加到代码中

所有弃用应该使用sympy.utilities.exceptions.sympy_deprecation_warning()。如果整个函数或方法已弃用,可以使用sympy.utilities.decorator.deprecated()装饰器。deprecated_since_versionactive_deprecations_target标志是必需的。请勿直接使用SymPyDeprecationWarning类来发出弃用警告。有关详细信息,请参阅sympy_deprecation_warning()的文档字符串。请参见下面的弃用文档以获取示例。

为已弃用的行为添加一个测试。使用sympy.testing.pytest.warns_deprecated_sympy()上下文管理器。

from sympy.testing.pytest import warns_deprecated_sympy

with warns_deprecated_sympy():
    <deprecated behavior> 

注意

warns_deprecated_sympy仅供 SymPy 测试套件内部使用。SymPy 的用户应直接使用warnings模块来过滤 SymPy 的弃用警告。请参阅静音 SymPy 弃用警告。

这有两个目的:测试警告是否正确发出,并测试已弃用的行为是否仍然有效。

如果要测试多个事物并断言每个事物都发出警告,则对每个事物使用单独的 with 块:

with warns_deprecated_sympy():
    <deprecated behavior1>
with warns_deprecated_sympy():
    <deprecated behavior2> 

这应该是唯一使用废弃行为的代码库和测试套件部分。其他所有内容都应更改为使用新的、非废弃的行为。SymPy 测试套件配置为,如果在任何地方发出SymPyDeprecationWarning,除了在warns_deprecated_sympy()块中,都将失败。您不应在废弃测试之外的任何地方使用此函数或warnings.filterwarnings(SymPyDeprecationWarning)。这包括废弃函数的文档示例。废弃函数的文档应该只有一个指向非废弃替代方法的注释。如果要在 doctest 中显示废弃函数,请使用# doctest: +SKIP。唯一的例外是您可以使用ignore_warnings(SymPyDeprecationWarning)来防止同一警告触发两次,即如果一个废弃函数调用另一个发出相同或类似警告的函数。

如果不可能在某处移除废弃的行为,则说明该行为尚未准备好被废弃。考虑到用户可能因为相同的原因无法替换废弃的行为。

记录废弃信息

所有废弃信息都应进行记录。每个废弃信息都需要在三个主要位置进行记录:

  • sympy_deprecation_warning() 警告文本。本文允许较长,以描述废弃情况,但不应超过一段。警告文本的主要目的是通知用户如何更新其代码。警告文本不应讨论为何功能被废弃或不必要的内部技术细节。此类讨论可放入下面提到的其他部分。不要在消息中包含已提供给sympy_deprecation_warning()关键字参数的元数据信息,如版本号或活动废弃文档的链接。请记住,警告文本将以纯文本形式显示,因此不要在文本中使用 RST 或 Markdown 标记。代码块应有明确的换行来使其易于阅读。警告消息中的所有文本应包装到 80 个字符,除了不能包装的代码示例。

    始终包含消息中废弃内容的完整上下文。例如,写“废弃了 abc 关键字到 func()”而不仅仅是“废弃了 abc 关键字”。这样,如果用户有一行较长的代码正在使用废弃功能,他们可以更容易地看到确切引发警告的部分。

  • 在相关文档字符串中添加一个弃用说明。这应该使用deprecated Sphinx 指令。使用语法.. deprecated:: <version>。如果整个函数都已弃用,则应将其放置在文档字符串顶部,正好在第一行下面。否则,如果只有部分函数已弃用(例如,单个关键字参数),则应将其放置在讨论该功能部分的文档字符串附近,例如在参数列表中。

    弃用文本应简短(不超过一个段落),解释何处已弃用以及用户应该使用什么代替。如果愿意,可以在此处使用与sympy_deprecation_warning()相同的文本。确保使用 RST 格式,包括与新函数相关的交叉引用,以及到active-deprecations.md文档中的更长描述的交叉引用(参见下文)。

    如果功能的文档与替换功能的文档相同(即,弃用只是对函数或参数的重命名),则可以用“请参阅<新功能>的文档”等备注替换其余文档。否则,应保留弃用功能的文档不变。

    这里有一些(虚构的)例子:

    @deprecated("""\
    The simplify_this(expr) function is deprecated. Use simplify(expr)
    instead.""", deprecated_since_version="1.1",
    active_deprecations_target='simplify-this-deprecation')
    def simplify_this(expr):
      """
     Simplify ``expr``.
    
     .. deprecated:: 1.1
    
     The ``simplify_this`` function is deprecated. Use :func:`simplify`
     instead. See its documentation for more information. See
     :ref:`simplify-this-deprecation` for details.
    
     """
        return simplify(expr) 
    
    def is_this_zero(x, y=0):
      """
     Determine if x = 0.
    
     Parameters
     ==========
    
     x : Expr
     The expression to check.
    
     y : Expr, optional
     If provided, check if x = y.
    
     .. deprecated:: 1.1
    
     The ``y`` argument to ``is_this_zero`` is deprecated. Use
     ``is_this_zero(x - y)`` instead. See
     :ref:`is-this-zero-y-deprecated` for more details.
    
     """
        if y != 0:
            sympy_deprecation_warning("""\
    The y argument to is_zero() is deprecated. Use is_zero(x - y) instead.""",
                deprecated_since_version="1.1",
                active_deprecations_target='is-this-zero-y-deprecation')
        return simplify(x - y) == 0 
    
  • 应该将弃用功能的更长描述添加到文档中列出所有当前活动弃用的页面(位于doc/src/explanation/active-deprecations.md中)中。

    这个页面是您可以深入了解弃用技术细节的地方。在这里,您还应列出为何某个功能被弃用的原因。您可以链接到相关问题、拉取请求和邮件列表讨论有关弃用的内容,但这些讨论应该总结,以便用户可以简要了解弃用的基本思想,而不必阅读旧讨论的页面。您还可以在这里提供在sympy_deprecation_warning()消息或.. deprecated::文本中无法容纳的更长示例。

    每个弃用都应该有一个交叉引用目标(使用 (target-name)= 放在章节标题上方),以便相关文档字符串中的 .. deprecated:: 笔记可以引用它。这个目标还应该传递给 sympy_deprecation_warning()@deprecatedactive_deprecations_target 选项。这将自动在警告消息的文档中链接到页面。目标名称应包含“deprecation”或“deprecated”这些词(Sphinx 中目标名称是全局的,因此目标名称在整个文档中必须是唯一的)。

    章节标题名称应该是被弃用的内容,应该是相应版本的三级标题(通常应添加到文件顶部)。

    如果多个弃用彼此相关,则可以共享本页的单个部分。

    如果弃用的函数未包含在顶级 sympy/__init__.py 中,请确保清楚指出对象所属的子模块。如果引用了 Sphinx 模块参考文档中的任何内容,请进行交叉引用,例如 {func}`~.func_name`

    注意这里的示例很有帮助,但通常不应使用文档测试来显示已弃用的功能,因为这将引发弃用警告并使文档测试失败。相反,您可以使用 # doctest: +SKIP,或者将示例显示为代码块而不是文档测试。

    这里是对应于(虚构的)上述示例的示例:

    (simplify-this-deprecation)=
    ### `simplify_this()`
    
    The `sympy.simplify.simplify_this()` function is deprecated. It has been
    replaced with the {func}`~.simplify` function. Code using `simplify_this()`
    can be fixed by replacing `simplfiy_this(expr)` with `simplify(expr)`. The
    behavior of the two functions is otherwise identical.
    
    This change was made because `simplify` is a much more Pythonic name than
    `simplify_this`. 
    
    (is-this-zero-y-deprecation)=
    ### `is_this_zero()` second argument
    The second argument to {func}`~.is_this_zero()` is deprecated. Previously
    `is_this_zero(x, y)` would check if x = y. However, this was removed because
    it is trivially equivalent to `is_this_zero(x - y)`. Furthermore, allowing
    to check $x=y$ in addition to just $x=0$ is is confusing given the function
    is named "is this zero".
    
    In particular, replace
    
    ```py
    
    is_this_zero(expr1, expr2)
    
    ```py
    
    with
    
    ```py
    
    is_this_zero(expr1 - expr2)
    
    ```py 
    

除了上述示例,SymPy 代码库中还有数十个现有弃用的示例,可以通过在 SymPy 代码库中搜索 sympy_deprecation_warning 找到。

发布说明条目

在拉取请求中,在发布说明部分记录破坏性更改使用 BREAKING CHANGE

一旦 PR 合并,您还应将其添加到即将发布的版本的“向后兼容性中断和弃用”部分的发布说明中。这需要手动完成,除了机器人的更改之外。参见 github.com/sympy/sympy/wiki/Writing-Release-Notes#user-content-backwards-compatibility-breaks-and-deprecations

每当在其弃用期后完全移除已弃用的功能时,这也需要标记为 BREAKING CHANGE 并添加到“向后兼容性中断和弃用”部分的发布说明中。

posted @ 2024-06-27 17:19  绝不原创的飞龙  阅读(22)  评论(0编辑  收藏  举报