我在注释 attrs 类属性时遇到问题。
我正在使用 NewType 来定义新的 UserId 类型和 attrs 冻结类。
这是 mypy 不会抱怨并且一切正常的代码:
from typing import NewType
from attr import frozen, field
UserId = NewType("UserId", str)
@frozen
class Order:
id: UserId = field()
mypy 检查此代码时没有任何问题。 使用 attrs 中的验证器后出现问题。
from typing import NewType
from attr import frozen, field, validators
UserId = NewType("UserId", str)
@frozen
class Order:
id: UserId = field(validator=validators.matches_re("^\d+$"))
mypy 现在抱怨类型不正确:
project/test_annotation.py:10:错误:赋值中的类型不兼容 (表达式的类型为“str”,变量的类型为“UserId”)[赋值]
在 1 个文件中发现 1 个错误(检查了 1 个源文件)
我现在不明白 field() 如何返回字符串类型。
有人可以解释一下吗?另外,我们如何解决这个问题?
环境:
Python 3.10.6
属性==22.1.0
cattrs==22.2.0
让它快乐,
cast
。 field
相当复杂,由于这个重载,你的
field
返回了str
:
... # other overloads
# This form catches an explicit None or no default and infers the type from the
# other arguments.
@overload
def field(
*,
default: None = ...,
validator: Optional[_ValidatorArgType[_T]] = ...,
repr: _ReprArgType = ...,
hash: Optional[bool] = ...,
init: bool = ...,
metadata: Optional[Mapping[Any, Any]] = ...,
converter: Optional[_ConverterType] = ...,
factory: Optional[Callable[[], _T]] = ...,
kw_only: bool = ...,
eq: Optional[_EqOrderType] = ...,
order: Optional[_EqOrderType] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
alias: Optional[str] = ...,
) -> _T: ...
它基本上是说“当
validators
是处理某种类型 T
的验证器[序列或其中之一]时,那么 field
返回 T
”。
因此,您传递了一个适用于
str
的验证器,因此 field
类型也是 str
。 NewType("UserID", str)
不是 str
的子类型,因此此分配失败。您有两个主要选择:
转换为所需类型:
from typing import cast
...
@frozen
class Order:
id: UserId = cast(str, field(validator=validators.matches_re("^\d+$")))
创建您自己的验证器。您不需要复制逻辑,只需更改签名并使用
type: ignore
或强制转换调用原始实现。
from typing import TYPE_CHECKING, Callable, Match, Optional, Pattern, Union, cast
if TYPE_CHECKING:
from attr import _ValidatorType
def userid_matches_re(
regex: Union[Pattern[str], str],
flags: int = ...,
func: Optional[
Callable[[str, str, int], Optional[Match[str]]]
] = ...,
) -> '_ValidatorType[UserID]':
return cast('_ValidatorType[UserID]', validators.matches_re(regex, flags, func))
...并在课堂上使用它而不是
validators.matches_re
。上面的签名是从 stubs 盗来的,其中 AnyStr
替换为 str
,因为无论如何你都不允许 bytes
。
我推荐第一个变体,因为另一种解决方案只是更多具有相同
cast
的样板,因此,它不会给你带来更多的安全性。但是,如果您有很多字段使用此验证器,那么它可能是可行的。