python-3.x 如何用验证器注解属性字段?

0x6upsns  于 2022-12-27  发布在  Python
关注(0)|答案(1)|浏览(133)

注解attrs类属性时遇到问题。
我使用NewType定义新的UserId类型和属性冻结类。
这段代码中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现在抱怨不正确的类型:
项目/测试注解. py:10:错误:赋值中的类型不兼容(表达式的类型为"str",变量的类型为"UserId")[赋值]
在1个文件中发现1个错误(已检查1个源文件)
我现在不明白field()如何返回字符串类型。
有人能解释一下吗?还有,我们如何解决这个问题?
环境:
Python 3.10.6语言
属性== 22.1.0
衰减== 22.2.0

vc9ivgsu

vc9ivgsu1#

为了使它更好,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类型也是strNewType("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。上面的签名是从存根中窃取的,其中AnyStr被替换为str,因为您无论如何都不允许使用bytes
我推荐第一个变体,因为另一个解决方案只是使用相同的cast的更多样板文件,因此它没有给你更多的安全性。但是,如果你有很多字段使用这个验证器,它可能是可行的。

相关问题