Mypy
Pydantic 与 mypy 配合得很好,开箱即用。
然而,Pydantic 还附带了一个 mypy 插件,该插件为 mypy 添加了许多重要的特定于 Pydantic 的功能,从而提高了它对代码进行类型检查的能力。
例如,考虑以下脚本:
from datetime import datetime
from typing import List, Optional
from pydantic import BaseModel
class Model(BaseModel):
age: int
first_name = 'John'
last_name: Optional[str] = None
signup_ts: Optional[datetime] = None
list_of_ints: List[int]
m = Model(age=42, list_of_ints=[1, '2', b'3'])
print(m.middle_name) # not a model field!
Model() # will raise a validation error for age and list_of_ints
在没有任何特殊配置的情况下,mypy 不会捕获缺失的模型字段注释,并警告关于 Pydantic 正确解析的 list_of_ints
参数:
test.py:15: error: List item 1 has incompatible type "str"; expected "int" [list-item]
test.py:15: error: List item 2 has incompatible type "bytes"; expected "int" [list-item]
test.py:16: error: "Model" has no attribute "middle_name" [attr-defined]
test.py:17: error: Missing named argument "age" for "Model" [call-arg]
test.py:17: error: Missing named argument "list_of_ints" for "Model" [call-arg]
但是启用了插件,它会给出正确的错误:
9: error: Untyped fields disallowed [pydantic-field]
16: error: "Model" has no attribute "middle_name" [attr-defined]
17: error: Missing named argument "age" for "Model" [call-arg]
17: error: Missing named argument "list_of_ints" for "Model" [call-arg]
使用 pydantic 的 mypy 插件,您可以放心地重构模型,因为如果您的字段名称或类型发生更改,mypy 将捕获任何错误。
还有其他好处!详情请见下文。
不使用插件使用 mypy¶
你可以使用:
mypy \
--ignore-missing-imports \
--follow-imports=skip \
--strict-optional \
pydantic_mypy_test.py
严格可选¶
为了使你的代码通过 --strict-optional
,你需要使用 Optional[]
或 Optional[]
的别名来代替所有默认值为 None
的字段。(这是 mypy 的标准做法。)
其他 Pydantic 接口¶
Pydantic 数据类和 validate_call
装饰器也应该与 mypy 配合良好。
Mypy 插件功能¶
为 Model.__init__
生成一个签名¶
-
任何没有动态确定别名的必填字段都将作为必填的关键字参数包含在内。
-
如果
Config.populate_by_name=True
,则生成的签名将使用字段名称,而不是别名。 -
如果
Config.extra='forbid'
和你不使用动态确定的别名,生成的签名将不允许意外输入。 -
可选:如果
init_forbid_extra
插件设置为True
,则对__init__
的意外输入即使Config.extra
不是'forbid'
也会引发错误。 -
可选:如果
init_typed
插件设置设置为True
,则生成的签名将使用模型字段的类型(否则将被注释为Any
以允许解析)。
为 Model.model_construct
生成带类型的签名¶
-
当输入数据已知有效且不应被解析时,
model_construct
方法是__init__
的一种替代方法。由于此方法在运行时不执行任何验证,因此静态检查对于检测错误很重要。
保留 Config.frozen
¶
-
如果
Config.frozen
是True
,那么当你尝试更改模型字段的值时,会得到一个 mypy 错误;可参看假不变性。
为 dataclasses
生成一个签名¶
-
用
@pydantic.dataclasses.dataclass
修饰的类与标准 Python 数据类的类型检查方式相同 -
@pydantic.dataclasses.dataclass
修饰符接受一个config
关键字参数,其含义与Config
子类相同。
尊重 Field
的 default
和 default_factory
的类型¶
-
字段同时具有
default
和default_factory
将在静态检查期间导致错误。 -
default
和default_factory
值的类型必须与字段的类型兼容。
警告使用未指定类型的字段¶
-
你在模型上分配公共属性而不注释其类型时,将得到一个 mypy 错误
-
如果你的目标是设置一个 ClassVar,你应该使用 typing.ClassVar 显式地注释该字段。
可选功能:¶
防止使用必需的动态别名¶
-
如果将
warn_required_dynamic_aliases
插件设置设置为True
,则在使用具有Config.populate_by_name=False
的模型上的动态确定别名或别名生成器时,将得到 mypy 错误。 -
这很重要,因为如果存在这样的别名,mypy 就不能正确地对
__init__
的调用进行类型检查。在这种情况下,它将默认将所有参数都视为可选的。
启用插件¶
要启用该插件,只需将 pydantic.mypy
添加到您的 mypy 配置文件中的插件列表中(这可能是 mypy.ini
、 pyproject.toml
或 setup.cfg
)。
要开始使用,您只需要创建一个带有以下内容的 mypy.ini
文件:
[mypy]
plugins = pydantic.mypy
注意
如果您使用的是 pydantic.v1
模型,您需要将 pydantic.v1.mypy
添加到您的插件列表中。
该插件与 mypy 版本 >=0.930
兼容。
请参考插件配置文档以获取更多详细信息。
配置插件¶
要更改插件设置的值,请在您的 mypy 配置文件中创建一个名为 [pydantic-mypy]
的部分,并添加任何要覆盖的设置的键值对。
启用了所有插件严格标志(以及其他一些 mypy 严格标志)的 mypy.ini
文件可能看起来像:
[mypy]
plugins = pydantic.mypy
follow_imports = silent
warn_redundant_casts = True
warn_unused_ignores = True
disallow_any_generics = True
check_untyped_defs = True
no_implicit_reexport = True
# for strict mypy: (this is the tricky one :-))
disallow_untyped_defs = True
[pydantic-mypy]
init_forbid_extra = True
init_typed = True
warn_required_dynamic_aliases = True
截至 mypy>=0.900
,mypy 配置也可能包含在 pyproject.toml
文件中,而不是 mypy.ini
中。与上述相同的配置为:
[tool.mypy]
plugins = [
"pydantic.mypy"
]
follow_imports = "silent"
warn_redundant_casts = true
warn_unused_ignores = true
disallow_any_generics = true
check_untyped_defs = true
no_implicit_reexport = true
# for strict mypy: (this is the tricky one :-))
disallow_untyped_defs = true
[tool.pydantic-mypy]
init_forbid_extra = true
init_typed = true
warn_required_dynamic_aliases = true
关于 --disallow-any-explicit
的说明¶
如果你正在使用 --disallow-any-explicit
mypy
配置设置(或其他禁止 Any
的设置),那么在扩展
BaseModel
时可能会遇到
no-any-explicit
错误。这是因为默认情况下,
Pydantic
的 mypy
插件会添加一个带有
init
方法的签名,类似于
def init(self, field_1: Any, field_2: Any, **kwargs: Any):
注释
“为什么要有额外的签名?”Pydantic mypy
插件添加了一个带有 __init__
签名的 def __init__(self, field_1: Any, field_2: Any, **kwargs: Any):
方法,以避免在使用与字段注释不匹配的类型初始化模型时出现类型错误。例如,如果没有这个 Any
签名, Model(date='2024-01-01')
会引发类型错误,但 Pydantic 有能力将字符串 '2024-01-01'
解析为 datetime.date
类型。
要解决此问题,您需要为 Pydantic mypy 插件启用严格模式设置。具体来说,在您的 [pydantic-mypy]
部分添加以下选项:
[tool.pydantic-mypy]
init_forbid_extra = true
init_typed = true
使用 init_forbid_extra = True
,从生成的 __init__
签名中删除 **kwargs
。使用 init_typed = True
,将字段的 Any
类型替换为其实际的类型提示。
这种配置允许你在 Pydantic 模型中使用 --disallow-any-explicit
而不会出现错误。但是,请注意,这种更严格的检查可能会将一些有效的 Pydantic 使用案例(例如将字符串传递给 datetime 字段)标记为类型错误。
本文总阅读量次