为代码库贡献#

代码标准#

编写优质代码不仅仅是关于你写了什么,更重要的是你如何编写它。在持续集成测试期间,将运行多个工具来检查你的代码是否存在风格错误。生成任何警告都会导致测试失败。因此,良好的风格是提交代码到 pandas 的必要条件。

pandas 中有一些工具可以帮助贡献者在贡献项目之前验证他们的更改。

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

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

此外,由于许多人使用我们的库,因此重要的是我们不要对代码进行突然的更改,这些更改可能会导致大量用户代码出现潜在的错误,也就是说,我们需要尽可能地向后兼容,以避免大规模的错误。

Pre-commit#

此外,持续集成 将运行代码格式检查,例如 blackruffisortclang-format 等,并使用 pre-commit 钩子。这些检查中的任何警告都将导致 持续集成 失败;因此,在提交代码之前自己运行检查将很有帮助。这可以通过安装 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

注意

如果您最近从上游分支合并了主分支,则 pre-commit 使用的一些依赖项可能已更改。请确保 更新您的开发环境

可选依赖项#

可选依赖项(例如 matplotlib)应使用私有助手 pandas.compat._optional.import_optional_dependency 导入。这确保在未满足依赖项时显示一致的错误消息。

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

所有可选依赖项都应在 可选依赖项 中进行记录,并且最小所需版本应在 pandas.compat._optional.VERSIONS 字典中设置。

向后兼容性#

请尽量保持向后兼容性。pandas 有很多用户,他们有大量的现有代码,所以如果可能的话,不要破坏它。如果您认为需要破坏,请在拉取请求中清楚地说明原因。此外,在更改方法签名时要小心,并在需要时添加弃用警告。另外,将弃用的 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 ... 约定。您的代码可能会被自动重写以使用一些现代结构(例如,使用内置的 list 而不是 typing.List),方法是通过 pre-commit 检查

在某些情况下,代码库中的类可能会定义覆盖内置函数的类变量。这会导致 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 静态分析代码库和类型提示。在进行任何更改后,您可以通过运行以下命令来确保类型提示一致

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 问题中获取。但是,始终值得考虑其他用例并编写相应的测试。

添加测试是代码推送到 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 固定装置测试 DataFrame/Series/Index/ExtensionArray 的行为。

    • tests.frame.test_arithmetic

    • tests.series.test_arithmetic

  4. 您的测试是否用于约简方法(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.get_locIndex.get_indexer)?此测试可能属于以下之一:

      • tests.indexes.test_indexing

      • tests.indexes.fooindex.test_indexing

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

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

    2. 测试是否针对 __getitem____setitem__ 以外的 Series 或 DataFrame 索引方法,例如 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 提供的扩展数组之一(CategoricalDatetimeArrayTimedeltaArrayPeriodArrayIntervalArrayNumpyExtensionArrayFloatArrayBoolArrayStringArray)吗?此测试可能属于以下目录之一:

    • tests.arrays

  2. 您的测试是针对所有扩展数组子类(“EA 接口”)吗?此测试可能属于以下目录之一:

    • tests.extension

使用 pytest#

测试结构#

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

class TestReallyCoolFeature:
    def test_cool_feature_aspect(self):
        pass

我们更喜欢使用 pytest 框架的更函数式风格,它提供更丰富的测试框架,将有助于测试和开发。因此,我们将编写类似于以下内容的测试函数,而不是编写测试类。

def test_really_cool_feature():
    pass

首选的 pytest 惯用法#

  • 名为 def test_* 的函数式测试,并且接受作为固定装置或参数的变量。

  • 对于测试标量和真值测试,使用裸 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

  • 如果多个测试可以共享一个设置对象,请使用 @pytest.fixture

警告

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

如果已知测试会失败,但失败的方式不应被捕获,请使用 pytest.mark.xfail。通常使用此方法来测试表现出错误行为或未实现的功能的测试。如果失败的测试具有不稳定的行为,请使用参数 strict=False。这将使 pytest 在测试通过时不会失败。使用 strict=False 非常不可取,请仅在万不得已时使用它。

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

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 夹具,并使用合成数据。

@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 问题编号作为注释。

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:xfailed 测试

  • X:xpassed 测试

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

如果你需要关于结果的帮助,这种情况在过去发生过,请在运行命令和打开错误报告之前设置一个种子,这样我们就可以重现它。以下是在 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 基准测试,以方便监控关键 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

所有运行基准测试的 asv 命令都应添加 -E virtualenv 选项。默认值在 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 [remaining arguments]。您可以使用现有 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:`1234`,其中 1234 是问题/拉取请求编号)。您的条目应使用完整的句子和正确的语法编写。

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

如果您的代码是错误修复,请将您的条目添加到相关的错误修复部分。避免添加到 Other 部分;只有在极少数情况下,条目才会出现在那里。尽可能简洁,错误的描述应包括用户如何遇到它以及错误本身的指示,例如“产生不正确的结果”或“错误地引发”。可能还需要指示新的行为。

如果您的代码是增强功能,则很可能需要在现有文档中添加使用示例。这可以通过遵循有关 文档 的部分来完成。此外,为了让用户知道何时添加了此功能,使用了 versionadded 指令。Sphinx 语法如下:

.. versionadded:: 2.1.0

这将在您放置 Sphinx 指令的任何位置放置文本 New in version 2.1.0。在添加新函数或方法 (示例) 或新关键字参数 (示例) 时,也应将其放在文档字符串中。