Mypy

在0.8.0中新增

Pandera与mypy集成,以提供数据框的静态类型检查,依赖于 pandas-stubs 获取类型信息。

pip install pandera[mypy]

然后在你的 mypy.inisetug.cfg 文件中启用插件:

[mypy]
plugins = pandera.mypy

注意

Mypy静态类型检查仅支持pandas数据框。

警告

此功能是实验性的 🧪。由于 pandas-stubs 类型存根 注释并不总是与官方 pandas 努力支持类型注释相匹配, 安装 pandera[mypy] 附加功能可能会在您的 pandas 代码中产生误报,其中许多已在 tests/mypy/modules 中记录(见这里)。

我们鼓励您提交问题,如果您发现任何错误的正面或负面报告由mypy。这样的错误列表可以在这里找到。我们很可能需要将此问题升级到官方pandas-stubs问题

另外,请注意,最新的pandas-stubs版本仅支持Python 3.8及以上版本。因此,如果您使用的是Python 3.7,在安装这个包时不会出现错误,但pip将安装一个带有过时类型注释的旧版本pandas-stubs。

在下面的示例中,我们定义了一些架构,以查看pandera的类型检查是如何工作的。

from typing import Optional, cast

import pandas as pd

import pandera as pa
from pandera.typing import DataFrame, Series


class Schema(pa.DataFrameModel):
    id: Series[int]
    name: Series[str]


class SchemaOut(pa.DataFrameModel):
    age: Series[int]


class AnotherSchema(pa.DataFrameModel):
    id: Series[int]
    first_name: Optional[Series[str]]

如果函数体的输出类型与函数的返回签名不匹配,mypy linter 将会发出警告。

def fn(df: DataFrame[Schema]) -> DataFrame[SchemaOut]:
    return df.assign(age=30).pipe(DataFrame[SchemaOut])  # mypy okay


def fn_pipe_incorrect_type(df: DataFrame[Schema]) -> DataFrame[SchemaOut]:
    return df.assign(age=30).pipe(DataFrame[AnotherSchema])  # mypy error
    # error: Argument 1 to "pipe" of "NDFrame" has incompatible type "Type[DataFrame[Any]]";  # noqa
    # expected "Union[Callable[..., DataFrame[SchemaOut]], Tuple[Callable[..., DataFrame[SchemaOut]], str]]"  [arg-type]  # noqa


def fn_assign_copy(df: DataFrame[Schema]) -> DataFrame[SchemaOut]:
    return df.assign(age=30)  # mypy error
    # error: Incompatible return value type (got "pandas.core.frame.DataFrame",
    # expected "pandera.typing.pandas.DataFrame[SchemaOut]")  [return-value]

如果输入类型与预期输入类型不匹配,它也会抱怨。请注意,我们正在使用pandera.typing.pandas.DataFrame泛型类型来定义在初始化时验证的dataframe,验证是针对DataFrameModel类型变量进行的。

schema_df = DataFrame[Schema]({"id": [1], "name": ["foo"]})
pandas_df = pd.DataFrame({"id": [1], "name": ["foo"]})
another_df = DataFrame[AnotherSchema]({"id": [1], "first_name": ["foo"]})


fn(schema_df)  # mypy okay

fn(pandas_df)  # mypy error
# error: Argument 1 to "fn" has incompatible type "pandas.core.frame.DataFrame";  # noqa
# expected "pandera.typing.pandas.DataFrame[Schema]"  [arg-type]

fn(another_df)  # mypy error
# error: Argument 1 to "fn" has incompatible type "DataFrame[AnotherSchema]";
# expected "DataFrame[Schema]"  [arg-type]

为了使mypy对返回类型感到满意,您可以初始化一个预期类型的数据框:

def fn_pipe_dataframe(df: DataFrame[Schema]) -> DataFrame[SchemaOut]:
    return df.assign(age=30).pipe(DataFrame[SchemaOut])  # mypy okay

注意

如果您使用上述方法与check_types()装饰器,pandera将尽力避免对数据框进行两次验证,如果它已经使用DataFrame[Schema](**data)语法初始化过。

或者使用 typing.cast() 告诉 mypy 函数的返回值是正确的类型。

def fn_cast_dataframe(df: DataFrame[Schema]) -> DataFrame[SchemaOut]:
    return cast(DataFrame[SchemaOut], df.assign(age=30))  # mypy okay

限制条件

使用pandera数据框类型进行静态类型检查的一个重要警告是,由于pandas数据框是可变对象,因此mypy无法知道一个被改变的DataFrameModel类型的数据框是否具有正确的内容。幸运的是,我们可以简单依赖check_types()装饰器来验证输出数据框是有效的。

请考虑下面的例子:

def fn_pipe_dataframe(df: DataFrame[Schema]) -> DataFrame[SchemaOut]:
    return df.assign(age=30).pipe(DataFrame[SchemaOut])  # mypy okay


def fn_cast_dataframe(df: DataFrame[Schema]) -> DataFrame[SchemaOut]:
    return cast(DataFrame[SchemaOut], df.assign(age=30))  # mypy okay


@pa.check_types
def fn_mutate_inplace(df: DataFrame[Schema]) -> DataFrame[SchemaOut]:
    out = df.assign(age=30).pipe(DataFrame[SchemaOut])
    out.drop(columns="age", inplace=True)
    return out  # okay for mypy, pandera raises error


@pa.check_types
def fn_assign_and_get_index(df: DataFrame[Schema]) -> DataFrame[SchemaOut]:
    return df.assign(foo=30).iloc[:3]  # mypy error

尽管这些函数的输出是不正确的,mypy 在静态类型检查时并不会捕获这个错误,但 pandera 会在运行时抛出一个SchemaErrorSchemaErrors异常,这取决于您是否正在进行延迟验证



@pa.check_types
def fn_cast_dataframe_invalid(df: DataFrame[Schema]) -> DataFrame[SchemaOut]:
    return cast(