贡献指南#
本文档重点概述了 cuDF 中的最佳实践。
目录结构和文件命名#
cuDF 通常提供与 pandas 相同的可导入模块和子包。所有 Cython 代码都包含在 python/cudf/cudf/_lib
中。
代码风格#
cuDF 使用了许多代码风格检查工具(linter),以确保代码库风格一致。我们使用 pre-commit
管理这些工具。强烈建议开发者在开始任何开发之前设置 pre-commit
。仓库根目录下的 .pre-commit-config.yaml
文件是代码风格检查的主要依据。具体来说,cuDF 使用以下工具:
Linter 配置数据存储在多个文件中。我们通常优先使用 pyproject.toml
而不是 setup.cfg
,并避免使用项目特定的文件(例如 pyproject.toml
优于 python/cudf/pyproject.toml
)。然而,由于工具之间以及仓库中不同包的差异,存在以下注意事项:
isort
必须按项目配置,以设置哪个项目是“第一方”项目。
因此,我们目前同时维护根目录和项目级别的 pyproject.toml
文件。
有关如何使用 pre-commit 钩子的更多信息,请参阅总体贡献指南的代码格式化部分。
废弃和移除代码#
cuDF 遵循在移除前一个版本中废弃代码的策略。例如,如果我们决定在 22.08 发布周期中移除一个 API,它将在 22.08 版本中标记为废弃,并在 22.10 版本中移除。请注意,如果评论中明确提及(如 # Do not remove until..
),请不要在评论中的条件满足之前强制执行废弃(即移除相关代码)。cuDF 中所有对废弃 API 的内部使用应在 API 被废弃时移除。这可以防止用户在使用其他(非废弃)API 时遇到意外的废弃警告。API 的文档也应更新以反映其废弃状态。当需要移除废弃的 API 时,请确保移除所有测试和文档。
废弃消息应:
发出
FutureWarning
;由单行组成,不含换行符;
如果存在替代 API,应予以说明(废弃消息是向用户展示更好方法的机会);
不指定何时进行移除(这会增加灵活性)。
例如:
warnings.warn(
"`Series.foo` is deprecated and will be removed in a future version of cudf. "
"Use `Series.new_foo` instead.",
FutureWarning
)
警告
废弃应使用 FutureWarning
发出信号,而不是 DeprecationWarning
!DeprecationWarning
默认是隐藏的,除非在 __main__
模块中运行的代码。
废弃信息也应在相应的公共 API 文档字符串中使用 deprecated
提示进行说明
.. deprecated:: 23.08
`foo` is deprecated and will be removed in a future version of cudf.
pandas
兼容性#
保持与 pandas API 的兼容性是 cuDF 的主要目标之一。开发者在向 cuDF 添加新功能时应始终参考 pandas API。当引入一个具有 pandas 对应功能的新 cuDF API 时,我们应尽可能与 pandas 匹配。由于我们尝试保持兼容性,即使是各种边缘情况(例如 null 处理),新的 pandas 发布版本有时需要进行更改,这可能会破坏与旧版本的兼容性。因此,我们的兼容性目标是最新的 pandas 版本。
然而,偶尔也有充分的理由偏离 pandas 的行为。最常见的原因围绕性能。某些 API 若要完全匹配 pandas 的行为,会产生过高的运行时成本。另一些可能需要使用额外的内存,这在 GPU 工作流程中总是非常宝贵的。如果你正在开发一个功能,并认为完美的 pandas 兼容性是不可行或不可取的,应与团队其他成员协商,评估如何继续。
当需要偏离 pandas 的行为时,应进行文档记录。有关如何操作的更多信息,请参阅我们关于 pandas 比较的文档。
Python vs Cython#
cuDF 大量使用了 Cython。Cython 是一个强大的工具,但不如纯 Python 用户友好。它也更难调试或分析性能。因此,在可能的情况下,开发者通常应优先选择 Python 代码而非 Cython。
Cython 在 cuDF 中的主要用例是将 libcudf C++ API 暴露给 Python。这种 Cython 用法通常由两部分组成:
一个
pxd
文件,声明 C++ API 以便在 Cython 中使用,以及一个
pyx
文件,包含封装这些 C++ API 的 Cython 函数,以便可以从 Python 调用它们。
后者的封装函数应尽量保持轻量,以最大程度地减少 Cython 的使用。更多信息请参见我们的 Cython 层设计文档。
在某些罕见情况下,我们可能通过编写纯 Cython 代码来加速特定的代码路径而获益。然而,考虑到 cuDF 中的大多数数值计算实际上发生在 libcudf 中,此类用例非常罕见。任何为此目的编写纯 Cython 代码的尝试都应通过基准测试来证明其合理性。
异常处理#
为了与保持与 pandas 的兼容性保持一致,cuDF 与 pandas 共享的任何 API 在给定相同输入时,应抛出与相应的 pandas API 完全相同的异常。但是,不需要匹配相应的 pandas API 的异常消息。
编写错误消息时,应包含足够的信息以帮助用户定位错误源,例如包括预期的参数类型与实际提供的参数。
对于尚不支持的参数,抛出 NotImplementedError
。无需提及参数未来何时会得到支持。
处理 libcudf 异常#
标准 C++ 原生支持各种异常类型,Cython 将其映射到这些 Python 异常类型。除了内置异常外,libcudf 还会抛出一些其他类型的异常。cuDF 扩展了 Cython 的默认映射以处理这些异常类型。当添加新的 libcudf 异常类型时,应在 cuDF 的异常处理程序中添加合适的 except 子句。如果没有合适的内置 Python 异常类型,则应创建一个新的 Python 异常。
抛出警告#
在适当的情况下,cuDF 应抛出与 pandas 相同的警告。例如,cuDF 中的 API 废弃应尽可能紧随 pandas。然而,并非所有 pandas 警告都适用于 cuDF。常见的例外情况包括不适用于 cuDF 内部的依赖于实现的性能警告。是否匹配 pandas 的决定必须根据具体情况作出,并取决于开发者和 PR 评审者的判断。
捕获来自依赖项的警告#
cuDF 开发者应避免使用包依赖项中已废弃的 API。然而,在某些情况下可能无法避免此类使用,至少在短期内是这样。如果出现这种情况,开发者应在 cuDF 内部捕获这些警告,以便用户不可见。