Mypy¶
在0.8.0中新增
Pandera与mypy集成,以提供数据框的静态类型检查,依赖于 pandas-stubs 获取类型信息。
pip install pandera[mypy]
然后在你的 mypy.ini 或 setug.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 会在运行时抛出一个SchemaError或SchemaErrors异常,这取决于您是否正在进行延迟验证。
@pa.check_types
def fn_cast_dataframe_invalid(df: DataFrame[Schema]) -> DataFrame[SchemaOut]:
return cast(