编写文档#
cuDF 文档分为多个部分。 所有核心功能都使用内联文档字符串进行记录。 用户指南或开发者指南等附加页面是独立编写的。 虽然文档字符串使用 reStructuredText (reST) 编写, 但后者使用 MyST 编写。 内联文档字符串使用一组额外的 reST 页面进行组织。 然后使用 Sphinx 将所有内容编译在一起。 本文档讨论了这些组件以及如何为它们做出贡献。
文档字符串#
cuDF 文档字符串使用 numpy 风格。 为了替代完整的解释, 我们在这里包含了一个格式示例和常用的部分:
class A:
"""Brief description of A.
Longer description of A.
Parameters
----------
x : int
Description of x, the first constructor parameter.
"""
def __init__(self, x: int):
pass
def foo(self, bar: str):
"""Short description of foo.
Longer description of foo.
Parameters
----------
bar : str
Description of bar.
Returns
-------
float
Description of the return value of foo.
Raises
------
ValueError
Explanation of when a ValueError is raised.
In this case, a ValueError is raised if bar is "fail".
Examples
--------
The examples section is _strongly_ encouraged.
Where appropriate, it may mimic the examples for the corresponding pandas API.
>>> a = A()
>>> a.foo('baz')
0.0
>>> a.foo('fail')
...
ValueError: Failed!
"""
if bar == "fail":
raise ValueError("Failed!")
return 0.0
numpydoc 支持许多其他部分的文档字符串。
开发者应该熟悉它们,因为许多在不同的场景中非常有用。
我们的指南包括对标准 numpydoc 指南的一个补充。
类属性,虽然没有明确涵盖,但应该在 getter 函数中进行文档化。
这种选择使得 help 更加有用,并且能够在子类中继承文档字符串。
我们所有的文档字符串都使用ruff pydocstyle rules进行验证。
这确保了文档字符串风格在代码库中保持一致和符合规范。
已发布的文档#
文档是使用Sphinx编译的,它从代码中提取文档字符串。然而,我们的目标不仅仅是简单地列出所有API,而是模仿pandas文档。为此,我们将API文档组织到特定的页面和部分中。这些页面存储在docs/cudf/source/api_docs中。例如,所有DataFrame文档都包含在docs/cudf/source/api_docs/dataframe.rst中。该页面包含“计算/描述性统计”等部分,以使API更易于发现。
在每个部分中,文档是使用autosummary创建的。这个插件使得为每个记录的API生成页面变得容易。为此,文档的每个部分看起来如下:
Section name
~~~~~~~~~~~~
.. autosummary::
API1
API2
...
每个列出的项目都会自动将其文档字符串渲染到一个单独的页面中。 此布局来自我们使用的Sphinx主题。
注意
在底层,autosummary 生成如下所示的存根页面(以 cudf.concat 为例):
cudf.concat
===========
.. currentmodule:: cudf
.. autofunction:: concat
像autofunction这样的命令来自autodoc。
这个指令将导入cudf并从cudf.concat中提取文档字符串。
这种方法使我们能够在组织文档时做最少的手动工作,
同时仍然尽可能接近地匹配pandas的布局。
在添加新API时,开发者只需将API添加到适当的页面。 将函数名称添加到适当的自动摘要列表中就足以记录它。
记录类#
Python 类和 RAPIDS 中使用的 Sphinx 插件以非平凡的方式交互。
autosummary 为类生成的默认页面使用 autodoc 自动检测并记录类的所有方法。
这意味着除了手动创建的 autosummary 页面(其中类方法按相关功能分组)之外,每个类还有另一个页面,其中该类的所有方法都自动汇总在一个表格中以便快速访问。
然而,我们还使用了 numpydoc 扩展,它提供了相同的功能。
我们同时使用两者,以尽可能匹配 pandas 文档的内容和风格。
pandas 对于类文档中包含的信息也非常讲究。
虽然主要面向用户的类的文档页面,如 DataFrame、Series 和 Index 包含了所有 API,但不太显眼的类或子类(如 Index 的子类)只包含那些特定于这些子类的方法。
例如,cudf.CategoricalIndex 只在其页面上包含 codes 和 categories,而不是整个 Index 功能集。
为了满足这些需求,我们采取以下方法:
默认的
autosummary类模板被一个更简单的模板所覆盖,该模板不生成方法或属性文档。换句话说,我们禁用了autosummary生成方法和属性列表的功能。我们完全依赖
numpydoc来列出需要完整 API 的类(DataFrame/Series/等)。如果(且仅当)方法和属性部分尚未在类的文档字符串中定义,numpydoc将自动填充这些部分。对于只应包含API子集的类,我们在类的文档中明确包含这些API。当这些列表存在时,
numpydoc不会覆盖它们。如果方法或属性部分应为空,则该部分仍必须包含,但应仅包含“None”。例如,CategoricalIndex类的文档可能包含如下内容:
Attributes
----------
codes
categories
Methods
-------
None
与pandas比较#
cuDF 旨在提供类似 pandas 的体验。
然而,由于各种原因,cuDF 的 API 可能与 pandas 存在差异。
如果存在这些差异,应该进行文档记录。
我们通过 pandas-compat 指令来促进此类文档记录。
该指令应在文档字符串中使用,如下所示:
"""Brief
Docstring body
.. pandas-compat::
:meth:`pandas.DataFrame.METHOD`
Explanation of differences
所有此类API兼容性说明都收集并显示在渲染的文档中。
编写文档页面#
除了文档字符串外,我们的文档还包含一些更专门的用户指南。
这些页面存储在docs/cudf/source/user_guide中。
这些页面都是使用MyST编写的,MyST是Markdown的超集。
MyST允许开发者使用熟悉的Markdown语法编写,
同时在需要时提供reST的全部功能。
这些页面并不符合任何特定的风格或用例集。
然而,如果您开发了任何足够复杂的新功能,
请考虑用户是否会从更完整的演示中受益。
注意
我们鼓励在页面之间使用链接。 我们启用了Myst自动生成的锚点, 因此链接应使用适当命名空间的锚点,而不是添加手动链接。
构建文档#
需求#
以下是构建文档所需的:
一个与RAPIDS兼容的GPU。这是必要的,因为文档会执行代码。
在相同的构建环境中拥有cudf的工作副本。 我们建议遵循构建说明。
Sphinx, numpydoc, 和 MyST-NB。 假设你遵循了构建说明,这些应该会自动安装到你的环境中。
构建和查看文档#
一旦你有了cudf的工作副本,构建文档就很简单了:
导航到
/path/to/cudf/docs/cudf/。执行
make html
这将在您的 shell 中运行 Sphinx 并在 build/html/index.html 生成输出。
查看结果。
导航到
build/html执行
python -m http.server
然后,打开一个网页浏览器并访问 https://localhost:8000。
如果当前有其他程序正在使用端口8000,
python -m http.server 会自动找到下一个可用的端口。
或者,您可以使用 python -m http.server $PORT 指定一个端口。
您可以在远程机器上构建文档,但希望在本地查看它们。假设另一台机器的IP地址在您的本地网络上可见,您可以通过将localhost替换为主机的IP地址来查看文档。或者,您也可以使用例如ssh -N -f -L localhost:$LOCAL_PORT:localhost:$REMOTE_PORT $REMOTE_IP来转发端口。这将使$REMOTE_IP:$REMOTE_PORT在localhost:$LOCAL_PORT上可见。
记录cuDF内部结构#
与公共API不同,内部代码(函数、类等)的文档没有经过检查。
强烈建议对内部代码进行文档化,但没有以任何特定方式强制执行。
关于风格,完整的numpy风格文档字符串或常规的#注释都是可以接受的。
前者对于复杂或广泛使用的功能可能很有用,
而后者对于小型一次性函数来说是可以的。