贡献代码库#

代码标准#

编写好的代码不仅在于你写了什么,还在于你如何编写。在持续集成测试期间,将运行几个工具来检查你的代码是否存在风格错误。生成任何警告都会导致测试失败。因此,良好的代码风格是向 pandas 提交代码的要求。

pandas 中有几个工具可以帮助贡献者在贡献项目之前验证其更改

  • ./ci/code_checks.sh:此脚本验证 doctests、docstrings 中的格式以及导入的模块。可以使用参数 docstringscodedoctests 独立运行检查(例如 ./ci/code_checks.sh doctests);

  • pre-commit,我们将在下一节详细介绍它。

此外,由于许多人使用我们的库,因此重要的是我们不要对代码进行可能导致大量用户代码中断的突然更改,也就是说,我们需要它尽可能保持向下兼容以避免大规模中断。

Pre-commit#

此外,持续集成将使用 pre-commit hooks 运行代码格式检查,例如 blackruffisortclang-format 等。这些检查中的任何警告都会导致持续集成失败;因此,在提交代码之前自己运行检查会很有帮助。这可以通过安装 pre-commit(如果你遵循了设置开发环境中的说明,这应该已经完成)然后运行以下命令来完成:

pre-commit install

在 pandas 仓库的根目录下运行。现在,每次提交更改时都会运行所有风格检查,而无需手动运行每一个。此外,使用 pre-commit 还可以让你随着代码检查的变化更轻松地保持最新状态。

请注意,如果需要,你可以使用 git commit --no-verify 跳过这些检查。

如果你不想将 pre-commit 作为工作流程的一部分使用,你仍然可以使用它来运行其检查,方法是使用以下命令之一:

pre-commit run --files <files you have modified>
pre-commit run --from-ref=upstream/main --to-ref=HEAD --all-files

无需事先运行 pre-commit install

最后,我们还有一些慢速的 pre-commit 检查,这些检查不会在每次提交时运行,但在持续集成期间会运行。你可以使用以下命令手动触发它们:

pre-commit run --hook-stage manual --all-files

注意

你可能希望定期运行 pre-commit gc 来清理不再使用的仓库。

注意

如果你的 virtualenv 安装冲突,则可能会出现错误 - 请参阅此处

此外,由于 virtualenv 中的一个错误,如果你使用 conda,可能会遇到问题。要解决此问题,你可以将 virtualenv 降级到版本 20.0.33

注意

如果你最近从上游分支合并了 main,pre-commit 使用的一些依赖项可能已经更改。请务必更新你的开发环境

可选依赖项#

可选依赖项(例如 matplotlib)应使用私有辅助函数 pandas.compat._optional.import_optional_dependency 进行导入。这确保了当依赖项未满足时提供一致的错误消息。

所有使用可选依赖项的方法都应包含一个测试,断言当找不到可选依赖项时会引发 ImportError。如果库存在,则应跳过此测试。

所有可选依赖项都应在可选依赖项中记录,并且最低要求的版本应在 pandas.compat._optional.VERSIONS 字典中设置。

向下兼容性#

请尽量保持向下兼容性。pandas 有许多用户和大量现有代码,因此请尽可能不要破坏它。如果你认为需要进行破坏性更改,请在拉取请求中清楚说明原因。此外,更改方法签名时要小心,并在需要时添加弃用警告。同时,为弃用的函数或方法添加 deprecated sphinx 指令。

如果存在参数与要弃用的函数相同的函数,你可以使用 pandas.util._decorators.deprecate

from pandas.util._decorators import deprecate

deprecate('old_func', 'new_func', '1.1.0')

否则,你需要手动完成

import warnings
from pandas.util._exceptions import find_stack_level


def old_func():
    """Summary of the function.

    .. deprecated:: 1.1.0
       Use new_func instead.
    """
    warnings.warn(
        'Use new_func instead.',
        FutureWarning,
        stacklevel=find_stack_level(),
    )
    new_func()


def new_func():
    pass

你还需要

  1. 编写一个新的测试,断言在使用弃用参数调用时会发出警告

  2. 更新所有 pandas 现有测试和代码以使用新参数

更多信息请参见测试警告

类型提示#

pandas 强烈建议使用 PEP 484 风格的类型提示。新开发的代码应该包含类型提示,也接受为现有代码添加类型提示的拉取请求!

风格指南#

类型导入应遵循 from typing import ... 的惯例。你的代码可能会被pre-commit 检查自动重写,以使用一些现代结构(例如使用内置的 list 而不是 typing.List)。

在代码库中,有时类会定义与内置函数同名的类变量。这会导致 Mypy 1775 中描述的问题。这里的防御性解决方案是创建一个内置函数明确的别名,并在不带注解的情况下使用它。例如,如果你遇到这样的定义:

class SomeClass1:
    str = None

正确的注解方式如下:

str_type = str

class SomeClass2:
    str: str_type = None

在某些情况下,当你比分析器更了解时,你可能想使用 typing 模块中的 cast。这在使用自定义推断函数时尤其常见。例如:

from typing import cast

from pandas.core.dtypes.common import is_number

def cannot_infer_bad(obj: Union[str, int, float]):

    if is_number(obj):
        ...
    else:  # Reasonably only str objects would reach this but...
        obj = cast(str, obj)  # Mypy complains without this!
        return obj.upper()

这里的局限性在于,尽管人类可以合理地理解 is_number 会捕获 intfloat 类型,但 mypy 尚不能做出同样的推断(参见 mypy #5206)。虽然上述方法有效,但强烈不鼓励使用 cast。在适用情况下,最好重构代码以满足静态分析的要求

def cannot_infer_good(obj: Union[str, int, float]):

    if isinstance(obj, str):
        return obj.upper()
    else:
        ...

对于自定义类型和推断,这并非总是可行,因此会做出例外处理,但在采取此类方法之前,应尽一切努力避免使用 cast

pandas 特有的类型#

pandas 特有的常用类型将出现在 pandas._typing 中,在适用情况下你应该使用这些类型。这个模块目前是私有的,但最终应该向想要对 pandas 进行类型检查的第三方库开放。

例如,pandas 中有很多函数接受 dtype 参数。它可以表示为字符串,如 "object",或 numpy.dtype,如 np.int64,甚至是 pandas ExtensionDtype,如 pd.CategoricalDtype。与其让用户不断地注解所有这些选项,不如直接从 pandas._typing 模块导入并重用它们

from pandas._typing import Dtype

def as_type(dtype: Dtype) -> ...:
    ...

该模块最终将包含重复使用的概念类型,例如“路径类似”、“数组类似”、“数值”等... 也可以包含常见参数的别名,例如 axis。该模块正在积极开发中,因此请务必参考源代码以获取最新可用类型列表。

验证类型提示#

pandas 使用 mypypyright 来静态分析代码库和类型提示。进行任何更改后,你可以通过在你的 Python 环境中运行以下命令来确保你的类型提示一致:

pre-commit run --hook-stage manual --all-files mypy
pre-commit run --hook-stage manual --all-files pyright
pre-commit run --hook-stage manual --all-files pyright_reportGeneralTypeIssues
# the following might fail if the installed pandas version does not correspond to your local git version
pre-commit run --hook-stage manual --all-files stubtest

(在你当前的 python 环境中)

警告

  • 请注意,以上命令将使用当前的 python 环境。如果你的 python 包比 pandas CI 安装的包更旧/更新,则以上命令可能会失败。这通常发生在 mypynumpy 版本不匹配时。请参阅如何设置 python 环境,或选择最近成功的 workflow,选择“Docstring validation, typing, and other manual pre-commit hooks”任务,然后单击“Set up Conda”和“Environment info”以查看 pandas CI 安装了哪些版本。

在使用 pandas 的代码中测试类型提示#

警告

  • Pandas 尚不是一个 py.typed 库(PEP 561)!在本地将 pandas 声明为 py.typed 库的主要目的是测试和改进 pandas 内置的类型注解。

在 pandas 成为 py.typed 库之前,可以通过在 pandas 安装文件夹中创建一个名为“py.typed”的空文件,轻松地试验 pandas 附带的类型注解:

python -c "import pandas; import pathlib; (pathlib.Path(pandas.__path__[0]) / 'py.typed').touch()"

py.typed 文件的存在向类型检查器表明 pandas 已经是一个 py.typed 库。这使得类型检查器了解 pandas 附带的类型注解。

使用持续集成进行测试#

提交拉取请求后,pandas 测试套件将自动在 GitHub Actions 持续集成服务上运行。但是,如果你希望在提交拉取请求之前在分支上运行测试套件,则需要将持续集成服务连接到你的 GitHub 仓库。有关 GitHub Actions 的说明在这里。

当你的构建全部“绿色”时,拉取请求将被考虑合并。如果有任何测试失败,你将看到红色的“X”,可以点击查看具体的失败测试。这是一个绿色构建的示例。

../_images/ci.png

测试驱动开发#

pandas 非常重视测试,并强烈鼓励贡献者采用测试驱动开发 (TDD)。这种开发过程“依赖于非常短的开发周期的重复:首先,开发者编写一个(最初失败的)自动化测试用例,该用例定义了所需的改进或新功能,然后生成最少量的代码以通过该测试。”因此,在实际编写任何代码之前,你应该编写测试。通常,测试可以来自原始的 GitHub issue。但是,考虑额外的用例并编写相应的测试总是值得的。

添加测试是代码推送到 pandas 后最常见的请求之一。因此,养成提前编写测试的习惯是值得的,这样就不会出现问题。

编写测试#

所有测试都应该放在特定包的 tests 子目录下。这个文件夹包含了许多当前的测试示例,我们建议参考这些示例以获取灵感。

作为一个通用提示,你可以使用集成开发环境 (IDE) 中的搜索功能或终端中的 git grep 命令来查找调用了该方法的测试文件。如果你不确定测试的最佳位置,可以先猜测一个位置,但请注意审阅者可能会要求你将测试移动到其他位置。

要使用 git grep,你可以在终端中运行以下命令:

git grep "function_name("

这将在你的仓库中的所有文件中搜索文本 function_name(。这是一种快速定位代码库中函数并确定为其添加测试的最佳位置的有用方法。

理想情况下,一个测试应该只有一个明确的位置。在我们达到这个理想之前,以下是一些关于测试位置的经验法则。

  1. 你的测试是否只依赖于 pd._libs.tslibs 中的代码?这个测试可能属于以下位置之一:

    • tests.tslibs

      注意

      tests.tslibs 中的任何文件都不应该导入 pd._libs.tslibs 之外的任何 pandas 模块

    • tests.scalar

    • tests.tseries.offsets

  2. 你的测试是否只依赖于 pd._libs 中的代码?这个测试可能属于以下位置之一:

    • tests.libs

    • tests.groupby.test_libgroupby

  3. 你的测试是针对算术或比较方法吗?这个测试可能属于以下位置之一:

    • tests.arithmetic

      注意

      这些测试旨在通过使用 box_with_array fixture 来共享测试 DataFrame/Series/Index/ExtensionArray 的行为。

    • tests.frame.test_arithmetic

    • tests.series.test_arithmetic

  4. 你的测试是针对 reduction 方法(min, max, sum, prod 等)吗?这个测试可能属于以下位置之一:

    • tests.reductions

      注意

      这些测试旨在共享,以测试 DataFrame/Series/Index/ExtensionArray 的行为。

    • tests.frame.test_reductions

    • tests.series.test_reductions

    • tests.test_nanops

  5. 你的测试是针对索引方法吗?这是决定测试位置最困难的情况,因为有许多此类测试,而且其中许多测试多个方法(例如 Series.__getitem__Series.loc.__getitem__ 都测试)

    1. 测试是否专门测试 Index 方法(例如 Index.get_loc, Index.get_indexer)?这个测试可能属于以下位置之一:

      • tests.indexes.test_indexing

      • tests.indexes.fooindex.test_indexing

      在这些文件中应该有一个特定于方法的测试类,例如 TestGetLoc

      在大多数情况下,这些测试中不需要 SeriesDataFrame 对象。

    2. 测试是针对 Series 或 DataFrame 的索引方法,但不是 __getitem____setitem__ 吗?例如 xswheretakemasklookupinsert?这个测试可能属于以下位置之一:

      • tests.frame.indexing.test_methodname

      • tests.series.indexing.test_methodname

    3. 测试是针对 locilocatiat 之一吗?这个测试可能属于以下位置之一:

      • tests.indexing.test_loc

      • tests.indexing.test_iloc

      • tests.indexing.test_at

      • tests.indexing.test_iat

      在相应的文件中,测试类对应于索引器类型(例如 TestLocBooleanMask)或主要用例(例如 TestLocSetitemWithExpansion)。

      请参阅 D) 节关于测试多个索引方法的注意事项。

    4. 测试是针对 Series.__getitem__Series.__setitem__DataFrame.__getitem__DataFrame.__setitem__ 吗?这个测试可能属于以下位置之一:

      • tests.series.test_getitem

      • tests.series.test_setitem

      • tests.frame.test_getitem

      • tests.frame.test_setitem

      在许多情况下,这样的测试可能会测试多个类似的方法,例如:

      import pandas as pd
      import pandas._testing as tm
      
      def test_getitem_listlike_of_ints():
          ser = pd.Series(range(5))
      
          result = ser[[3, 4]]
          expected = pd.Series([2, 3])
          tm.assert_series_equal(result, expected)
      
          result = ser.loc[[3, 4]]
          tm.assert_series_equal(result, expected)
      

    在这种情况下,测试位置应基于正在测试的底层方法。或者在修复 bug 的测试中,基于实际 bug 的位置。因此在此示例中,我们知道 Series.__getitem__ 调用了 Series.loc.__getitem__,所以这实际上loc.__getitem__ 的测试。因此此测试属于 tests.indexing.test_loc

  6. 你的测试是针对 DataFrame 或 Series 方法吗?

    1. 方法是绘图方法吗?这个测试可能属于以下位置之一:

      • tests.plotting

    2. 方法是 IO 方法吗?这个测试可能属于以下位置之一:

      • tests.io

        注意

        这包括 to_string 但不包括 __repr__,后者在 tests.frame.test_reprtests.series.test_repr 中进行测试。其他类通常有一个 test_formats 文件。

    3. 否则,这个测试可能属于以下位置之一:

      • tests.series.methods.test_mymethod

      • tests.frame.methods.test_mymethod

        注意

        如果测试可以使用 frame_or_series fixture 在 DataFrame/Series 之间共享,按照惯例它会放在 tests.frame 文件中。

  7. 你的测试是针对 Index 方法,且不依赖于 Series/DataFrame 吗?这个测试可能属于以下位置之一:

    • tests.indexes

  1. 你的测试是针对 pandas 提供的 ExtensionArrays (Categorical, DatetimeArray, TimedeltaArray, PeriodArray, IntervalArray, NumpyExtensionArray, FloatArray, BoolArray, StringArray) 之一吗?这个测试可能属于以下位置之一:

    • tests.arrays

  2. 你的测试是针对所有 ExtensionArray 子类(“EA Interface”)吗?这个测试可能属于以下位置之一:

    • tests.extension

使用 pytest#

测试结构#

pandas 现有的测试结构主要是基于类的,这意味着你通常会发现测试被封装在一个类中。

class TestReallyCoolFeature:
    def test_cool_feature_aspect(self):
        pass

我们更喜欢使用 pytest 框架的更函数式的风格,它提供了一个更丰富的测试框架,将促进测试和开发。因此,我们将编写测试函数,而不是编写测试类,如下所示:

def test_really_cool_feature():
    pass

首选的 pytest 用法#

  • 命名为 def test_* 的函数式测试并且接受 fixture 或参数作为参数。

  • 使用裸露的 assert 进行标量和真值测试

  • 使用 tm.assert_series_equal(result, expected)tm.assert_frame_equal(result, expected) 分别用于比较 SeriesDataFrame 的结果。

  • 在测试多个用例时使用 @pytest.mark.parameterize

  • 当测试用例预期会失败时使用 pytest.mark.xfail

  • 当测试用例绝不会通过时使用 pytest.mark.skip

  • 当测试用例需要特定的标记时使用 pytest.param

  • 如果多个测试可以共享 setup 对象,则使用 @pytest.fixture

警告

不要使用 pytest.xfail(这与 pytest.mark.xfail 不同),因为它会立即停止测试,并且不检查测试是否会失败。如果你希望这种行为,请改用 pytest.skip

如果已知某个测试会失败,但其失败的方式不需要被捕获,请使用 pytest.mark.xfail。通常用于测试显示有 bug 的行为或未实现的特性。如果失败的测试具有不稳定行为,请使用参数 strict=False。这样即使测试偶然通过,pytest 也不会将其标记为失败。使用 strict=False 非常不推荐,请仅作为最后手段使用。

优先使用装饰器 @pytest.mark.xfail 和参数 pytest.param,而不是在测试内部使用,以便在 pytest 的收集阶段正确标记测试。对于涉及多个参数、fixture 或它们的组合的测试,只能在测试阶段进行 xfail。为此,请使用 request fixture

def test_xfail(request):
    mark = pytest.mark.xfail(raises=TypeError, reason="Indicate why here")
    request.applymarker(mark)

xfail 不应用于因无效用户参数导致的失败测试。对于这些测试,我们需要验证是否抛出了正确的异常类型和错误消息,请改用 pytest.raises

测试警告#

使用 tm.assert_produces_warning 作为上下文管理器,来检查代码块是否触发警告。

with tm.assert_produces_warning(DeprecationWarning):
    pd.deprecated_function()

如果代码块中不应出现警告,请将 False 传递给上下文管理器。

with tm.assert_produces_warning(False):
    pd.no_warning_function()

如果您的测试会发出警告,但您实际上并不是在测试警告本身(例如,因为它将来会被移除,或者因为我们正在匹配第三方库的行为),那么请使用 pytest.mark.filterwarnings 来忽略错误。

@pytest.mark.filterwarnings("ignore:msg:category")
def test_thing(self):
    pass

测试异常#

使用 pytest.raises 作为上下文管理器,配合特定的异常子类(即,永远不要使用 Exception),并在 match 中指定异常消息。

with pytest.raises(ValueError, match="an error"):
    raise ValueError("an error")

涉及文件的测试#

tm.ensure_clean 上下文管理器会创建一个用于测试的临时文件,文件名是自动生成的(如果提供了文件名则使用您的文件名),并在上下文块退出时自动删除该文件。

with tm.ensure_clean('my_file_path') as path:
    # do something with the path

涉及网络连接的测试#

单元测试不应通过互联网访问公共数据集,因为网络连接不稳定且无法控制所连接的服务器。为了模拟这种交互,请使用来自 pytest-localserver 插件httpserver fixture,并使用合成数据。

@pytest.mark.network
@pytest.mark.single_cpu
def test_network(httpserver):
    httpserver.serve_content(content="content")
    result = pd.read_html(httpserver.url)

示例#

这是一个包含在一文件 pandas/tests/test_cool_feature.py 中的自包含测试集示例,它演示了我们喜欢使用的多种特性。请记住在新测试中添加 GitHub Issue 编号作为注释。

import pytest
import numpy as np
import pandas as pd


@pytest.mark.parametrize('dtype', ['int8', 'int16', 'int32', 'int64'])
def test_dtypes(dtype):
    assert str(np.dtype(dtype)) == dtype


@pytest.mark.parametrize(
    'dtype', ['float32', pytest.param('int16', marks=pytest.mark.skip),
              pytest.param('int32', marks=pytest.mark.xfail(
                  reason='to show how it works'))])
def test_mark(dtype):
    assert str(np.dtype(dtype)) == 'float32'


@pytest.fixture
def series():
    return pd.Series([1, 2, 3])


@pytest.fixture(params=['int8', 'int16', 'int32', 'int64'])
def dtype(request):
    return request.param


def test_series(series, dtype):
    # GH <issue_number>
    result = series.astype(dtype)
    assert result.dtype == dtype

    expected = pd.Series([1, 2, 3], dtype=dtype)
    tm.assert_series_equal(result, expected)

运行此测试会产生

((pandas) bash-3.2$ pytest  test_cool_feature.py  -v
=========================== test session starts ===========================
platform darwin -- Python 3.6.2, pytest-3.6.0, py-1.4.31, pluggy-0.4.0
collected 11 items

tester.py::test_dtypes[int8] PASSED
tester.py::test_dtypes[int16] PASSED
tester.py::test_dtypes[int32] PASSED
tester.py::test_dtypes[int64] PASSED
tester.py::test_mark[float32] PASSED
tester.py::test_mark[int16] SKIPPED
tester.py::test_mark[int32] xfail
tester.py::test_series[int8] PASSED
tester.py::test_series[int16] PASSED
tester.py::test_series[int32] PASSED
tester.py::test_series[int64] PASSED

我们已经 parametrized 的测试现在可以通过测试名称访问,例如,我们可以使用 -k int8 运行这些测试,以仅选择匹配 int8 的测试。

((pandas) bash-3.2$ pytest  test_cool_feature.py  -v -k int8
=========================== test session starts ===========================
platform darwin -- Python 3.6.2, pytest-3.6.0, py-1.4.31, pluggy-0.4.0
collected 11 items

test_cool_feature.py::test_dtypes[int8] PASSED
test_cool_feature.py::test_series[int8] PASSED

使用 hypothesis#

Hypothesis 是一个用于基于属性测试的库。与其明确地参数化测试,不如描述 所有 有效输入,然后让 Hypothesis 尝试找到一个失败的输入。更好的是,无论它尝试多少随机示例,Hypothesis 总是会报告一个单一的最小反例来否定您的断言 - 通常是您从未想过要测试的示例。

有关更多介绍,请参阅 Hypothesis 入门,然后 参阅 Hypothesis 文档获取详细信息

import json
from hypothesis import given, strategies as st

any_json_value = st.deferred(lambda: st.one_of(
    st.none(), st.booleans(), st.floats(allow_nan=False), st.text(),
    st.lists(any_json_value), st.dictionaries(st.text(), any_json_value)
))


@given(value=any_json_value)
def test_json_roundtrip(value):
    result = json.loads(json.dumps(value))
    assert value == result

此测试展示了 Hypothesis 的一些有用特性,并演示了一个良好的用例:检查应在大量或复杂输入领域中保持的属性。

为了保持 pandas 测试套件运行快速,如果输入或逻辑简单,则首选参数化测试;Hypothesis 测试则保留用于逻辑复杂、选项组合过多或细微交互难以测试(或难以想到!)所有这些情况。

运行测试套件#

然后可以直接在您的 Git 克隆目录中运行测试(无需安装 pandas),方法是输入

pytest pandas

注意

如果少量测试未通过,可能不是您的 pandas 安装问题。有些测试(例如一些 SQLAlchemy 测试)需要额外设置,其他测试可能会因为未锁定的库发布新版本而开始失败,还有些测试如果并行运行可能会不稳定。只要您能够从本地构建的版本中导入 pandas,您的安装可能就没问题,您可以开始贡献了!

通常,在运行整个套件之前,首先只运行围绕您的更改的测试子集是值得的(提示:您可以使用 pandas-coverage 应用 来查找哪些测试触及了您修改的代码行,然后只运行这些测试)。

最简单的方法是使用

pytest pandas/path/to/test.py -k regex_matching_test_name

或使用以下构造之一

pytest pandas/tests/[test-module].py
pytest pandas/tests/[test-module].py::[TestClass]
pytest pandas/tests/[test-module].py::[TestClass]::[test_method]

使用 pytest-xdist(包含在我们的 'pandas-dev' 环境中),可以在多核机器上加速本地测试。运行 pytest 时可以指定 -n 数字标志,以便在指定的核数之间并行运行测试,或者指定 auto 以利用机器上所有可用核。

# Utilize 4 cores
pytest -n 4 pandas

# Utilizes all available cores
pytest -n auto pandas

如果您想进一步加快速度,更高级的用法如下

pytest pandas -n 4 -m "not slow and not network and not db and not single_cpu" -r sxX

除了多线程性能提升外,这还通过使用 -m 标记跳过了一些测试来提高测试速度。

  • slow:任何需要很长时间的测试(以秒计,而不是毫秒)

  • network:需要网络连接的测试

  • db:需要数据库(mysql 或 postgres)的测试

  • single_cpu:只能在单核 CPU 上运行的测试

如果与您相关,您可能想启用以下选项

  • arm_slow:在 arm64 架构上需要很长时间的测试

这些标记定义在 此 toml 文件[tool.pytest.ini_options] 下,在一个名为 markers 的列表中,以便您可以检查是否创建了您感兴趣的新标记。

-r 报告标志将显示一个简短的摘要信息(请参阅 pytest 文档)。此处我们显示了以下数量:

  • s: 跳过的测试

  • x: 预期失败的测试

  • X: 预期失败但通过的测试

摘要是可选的,如果您不需要额外信息,可以将其移除。使用并行化选项可以显著减少在提交拉取请求之前本地运行测试所需的时间。

如果您需要协助处理结果(过去曾发生过这种情况),请在运行命令和提交错误报告之前设置种子,这样我们就可以重现该问题。以下是在 Windows 上设置种子的示例

set PYTHONHASHSEED=314159265
pytest pandas -n 4 -m "not slow and not network and not db and not single_cpu" -r sxX

在 Unix 上使用

export PYTHONHASHSEED=314159265
pytest pandas -n 4 -m "not slow and not network and not db and not single_cpu" -r sxX

更多信息请参阅 pytest 文档。

此外,可以运行

pd.test()

配合已导入的 pandas 来类似地运行测试。

运行性能测试套件#

性能很重要,考虑您的代码是否引入了性能回归是值得的。pandas 正在迁移到 asv benchmarks,以便轻松监控关键 pandas 操作的性能。这些基准测试都位于 pandas/asv_bench 目录中,测试结果可以在 这里 找到。

要使用 asv 的所有功能,您需要 condavirtualenv。有关更多详细信息,请查看 asv 安装网页

安装 asv

pip install git+https://github.com/airspeed-velocity/asv

如果您需要运行基准测试,请将目录更改为 asv_bench/ 并运行

asv continuous -f 1.1 upstream/main HEAD

您可以将 HEAD 替换为您正在工作的分支名称,并报告变化超过 10% 的基准测试。该命令默认使用 conda 来创建基准测试环境。如果您想使用 virtualenv,请写入

asv continuous -f 1.1 -E virtualenv upstream/main HEAD

-E virtualenv 选项应添加到所有运行基准测试的 asv 命令中。默认值在 asv.conf.json 中定义。

运行完整的基准测试套件可能需要一整天,具体取决于您的硬件及其资源利用率。然而,通常只需将部分结果粘贴到拉取请求中,以表明提交的更改不会导致意外的性能回归。您可以使用 -b 标志运行特定基准测试,该标志接受一个正则表达式。例如,这将仅运行来自 pandas/asv_bench/benchmarks/groupby.py 文件的基准测试

asv continuous -f 1.1 upstream/main HEAD -b ^groupby

如果您只想运行文件中特定的一组基准测试,可以使用 . 作为分隔符。例如

asv continuous -f 1.1 upstream/main HEAD -b groupby.GroupByMethods

将仅运行在 groupby.py 中定义的 GroupByMethods 基准测试。

您还可以使用当前 Python 环境中已安装的 pandas 版本运行基准测试套件。如果您没有 virtualenv 或 conda,或者正在使用上面讨论的 setup.py develop 方法,这会很有用;对于原地构建,您需要设置 PYTHONPATH,例如 PYTHONPATH="$PWD/.." asv [剩余参数]。您可以通过以下方式使用现有 Python 环境运行基准测试:

asv run -e -E existing

或者,要使用特定的 Python 解释器,

asv run -e -E existing:python3.6

这将显示基准测试的 stderr 输出,并使用您的 $PATH 中本地的 python

有关如何编写基准测试以及如何使用 asv 的信息,请参阅 asv 文档

文档化您的代码#

更改应反映在位于 doc/source/whatsnew/vx.y.z.rst 的发布说明中。此文件包含每个版本的持续更新日志。在此文件中添加条目以记录您的修复、增强或(不可避免的)破坏性更改。添加条目时,请务必包含 GitHub issue 编号(使用 :issue:`1234`,其中 1234 是 issue/pull request 编号)。您的条目应使用完整的句子和正确的语法书写。

提及 API 的部分时,请酌情使用 Sphinx 的 :func::meth::class: 指令。并非所有公共 API 函数和方法都有文档页面;理想情况下,只有在能够解析时才添加链接。您通常可以通过查看之前版本的发布说明来找到类似的示例。

如果您的代码是错误修复,请将您的条目添加到相关的错误修复部分。避免添加到 Other 部分;只有在极少数情况下才应将条目放在此处。描述错误时应尽量简洁,包括用户可能如何遇到该错误以及错误本身的迹象,例如“产生错误结果”或“错误地引发异常”。可能还需要说明新的行为。

如果您的代码是增强功能,则很可能需要向现有文档添加使用示例。这可以按照有关 文档贡献 的部分进行。此外,为了让用户知道何时添加了此功能,使用了 versionadded 指令。其 sphinx 语法为

.. versionadded:: 2.1.0

无论您将 sphinx 指令放在哪里,这将显示文本 New in version 2.1.0。在添加新函数或方法(示例)或新关键字参数(示例)时,这也应放在 docstring 中。