可重用约束类型,用于与typing.Annotated一起使用
项目描述
annotated-types
PEP-593 将 typing.Annotated
添加为向现有类型添加上下文特定元数据的方式,并指定任何工具或库在不需要为 x
特殊逻辑的情况下应将 Annotated[T, x]
视为 T
。
此软件包提供了可以用于表示常见约束的元数据对象,例如标量值的上界和下界以及集合大小,一个用于运行时检查的 Predicate
标记,以及我们打算如何解释这些元数据的说明。在某些情况下,我们还注明了不需要此软件包的替代表示。
安装
pip install annotated-types
示例
from typing import Annotated
from annotated_types import Gt, Len, Predicate
class MyClass:
age: Annotated[int, Gt(18)] # Valid: 19, 20, ...
# Invalid: 17, 18, "19", 19.0, ...
factors: list[Annotated[int, Predicate(is_prime)]] # Valid: 2, 3, 5, 7, 11, ...
# Invalid: 4, 8, -2, 5.0, "prime", ...
my_list: Annotated[list[int], Len(0, 10)] # Valid: [], [10, 20, 30, 40, 50]
# Invalid: (1, 2), ["abc"], [0] * 20
文档
尽管 annotated-types
为了性能避免运行时检查,用户不应构建无效的组合,例如 MultipleOf("non-numeric")
或 Annotated[int, Len(3)]
。如果使用以下描述的元数据对象与不兼容的类型,下游实现者可以选择引发错误、发出警告、静默忽略元数据项等——或者出于任何其他原因!
Gt, Ge, Lt, Le
表示可排序值的包含和/或非包含边界——这些值可以是数字、日期、时间、字符串、集合等。请注意,边界值不需要与注释的类型相同,只要它们可以进行比较:例如,Annotated[int, Gt(1.5)]
是可以的,它意味着值是一个整数 x,使得 x > 1.5
。
我们建议实现者可以将 functools.partial(operator.le, 1.5)
解释为与 Gt(1.5)
等效,以便希望避免对 annotated-types
包的运行时依赖的用户。
明确来说,这些类型具有以下含义
Gt(x)
- 值必须大于x
- 等同于最小值Ge(x)
- 值必须大于或等于x
- 等同于最小值Lt(x)
- 值必须小于x
- 等同于最大值Le(x)
- 值必须小于或等于x
- 等同于最大值
区间
Interval(gt, ge, lt, le)
允许您使用单个元数据对象指定上下限。应忽略 None
属性,并按照上述单个边界处理非 None
属性。
MultipleOf
MultipleOf(multiple_of=x)
可以有两种解释方式
- Python 语义,意味着
value % multiple_of == 0
,或者 - JSONschema 语义,其中
int(value / multiple_of) == value / multiple_of
。
我们鼓励用户了解这两种常见的解释及其不同的行为,尤其是对于非常大的或非整数数字,由于浮点数精度问题,很容易导致静默数据损坏。
我们鼓励库仔细记录它们实现了哪种解释。
MinLen, MaxLen, Len
Len()
表示 min_length <= len(value) <= max_length
- 下限和上限都是包含的。
除了可选包含上下限的 Len()
之外,我们还提供了 MinLen(x)
和 MaxLen(y)
,它们分别等同于 Len(min_length=x)
和 Len(max_length=y)
。
Len
、MinLen
和 MaxLen
可以与任何支持 len(value)
的类型一起使用。
使用示例
Annotated[list, MaxLen(10)]
(或Annotated[list, Len(max_length=10))
)- 列表长度必须为 10 或更短Annotated[str, MaxLen(10)]
- 字符串长度必须为 10 或更短Annotated[list, MinLen(3))
(或Annotated[list, Len(min_length=3))
)- 列表长度必须为 3 或更多Annotated[list, Len(4, 6)]
- 列表长度必须为 4、5 或 6Annotated[list, Len(8, 8)]
- 列表长度必须正好为 8
在 v0.4.0 中更改
min_inclusive
已重命名为min_length
,含义未变max_exclusive
已重命名为max_length
,上界现在为 包含的 而不是 排除的- 有关切片的解释为
Len
的建议已被删除,因为存在歧义,并且切片与Len
的上界语义不同
有关讨论,请参阅 问题 #23。
时区
时区
可以与 datetime
或 time
一起使用,以表示允许哪些时区。 Annotated[datetime, Timezone(None)]
必须是 naive datetime。 Timezone[...]
(文字省略号) 表示允许任何具有时区感知的 datetime。您还可以传递特定的时区字符串或 tzinfo
对象,如 Timezone(timezone.utc)
或 Timezone("Africa/Abidjan")
,以表示您只允许特定的时区,尽管我们注意到这通常是脆弱设计的一个症状。
在 v0.x.x 中更改
单位
Unit(unit: str)
表示注释的数值是具有指定单位的量的幅度。例如,Annotated[float, Unit("m/s")]
将是一个表示每秒米数的速度的浮点数。
请注意,annotated_types
本身不尝试以任何方式解析或验证单位字符串。这完全留给下游库,例如 pint
或 astropy.units
。
一个库如何使用此元数据的示例
from annotated_types import Unit
from typing import Annotated, TypeVar, Callable, Any, get_origin, get_args
# given a type annotated with a unit:
Meters = Annotated[float, Unit("m")]
# you can cast the annotation to a specific unit type with any
# callable that accepts a string and returns the desired type
T = TypeVar("T")
def cast_unit(tp: Any, unit_cls: Callable[[str], T]) -> T | None:
if get_origin(tp) is Annotated:
for arg in get_args(tp):
if isinstance(arg, Unit):
return unit_cls(arg.unit)
return None
# using `pint`
import pint
pint_unit = cast_unit(Meters, pint.Unit)
# using `astropy.units`
import astropy.units as u
astropy_unit = cast_unit(Meters, u.Unit)
谓词
Predicate(func: Callable)
表示 func(value)
对于有效值来说是 truthy。用户应优先考虑上述静态可检查的元数据,但如果您需要任意运行时谓词的完整功能和灵活性……这里就是。
对于一些常见的约束,我们提供通用类型
IsLower = Annotated[T, Predicate(str.islower)]
IsUpper = Annotated[T, Predicate(str.isupper)]
IsDigit = Annotated[T, Predicate(str.isdigit)]
IsFinite = Annotated[T, Predicate(math.isfinite)]
IsNotFinite = Annotated[T, Predicate(Not(math.isfinite))]
IsNan = Annotated[T, Predicate(math.isnan)]
IsNotNan = Annotated[T, Predicate(Not(math.isnan))]
IsInfinite = Annotated[T, Predicate(math.isinf)]
IsNotInfinite = Annotated[T, Predicate(Not(math.isinf))]
因此,您可以用 x: IsFinite[float] = 2.0
来编写,而不是更长的(但完全等价)的 x: Annotated[float, Predicate(math.isfinite)] = 2.0
。
一些库可能有特殊的逻辑来处理已知或可理解的谓词,例如通过检查 str.isdigit
并使用其存在来调用自定义逻辑以强制执行仅包含数字的字符串,并自定义一些生成的外部模式。因此,鼓励用户避免使用 lambda s: s.lower()
这样的间接方法,而应使用可内省的方法,如 str.lower
或 re.compile("pattern").search
。
为了在不引入使实现者无法内省谓词的 introspection 的情况下启用对常用谓词(如 math.isnan
)的基本否定,我们提供了一个 Not
包装器,该包装器简单地以内省方式否定谓词。上述列出的许多谓词都是以这种方式创建的。
我们没有指定应期望抛出异常的谓词的行为。例如,Annotated[int, Predicate(str.isdigit)]
可能会静默跳过无效约束,或静态引发错误;或者它可能尝试调用它,然后传播或丢弃产生的 TypeError: descriptor 'isdigit' for 'str' objects doesn't apply to a 'int' object
异常。我们鼓励库记录它们选择的行为。
文档
doc()
可以用于在 Annotated
中添加文档信息,用于函数和方法参数、变量、类属性、返回类型以及 Annotated
可以使用的任何地方。
它期望一个可以静态分析的值,因为主要用例是静态分析、编辑器、文档生成器等工具。
它返回一个具有单个属性 documentation
的 DocInfo
类,其中包含传递给 doc()
的值。
这是早期采用者对 typing-doc
提案 的替代形式。
将下游类型与 GroupedMetadata
集成
实现者可以选择提供一种方便的包装器,将多个元数据分组。这可以帮助减少用户在认知上的负担。例如,像 Pydantic 这样的实现者可能会提供一个接受关键字参数并将它们转换为低级元数据的 Field
或 Meta
类型。
from dataclasses import dataclass
from typing import Iterator
from annotated_types import GroupedMetadata, Ge
@dataclass
class Field(GroupedMetadata):
ge: int | None = None
description: str | None = None
def __iter__(self) -> Iterator[object]:
# Iterating over a GroupedMetadata object should yield annotated-types
# constraint metadata objects which describe it as fully as possible,
# and may include other unknown objects too.
if self.ge is not None:
yield Ge(self.ge)
if self.description is not None:
yield Description(self.description)
消耗注解类型约束的库应检查 GroupedMetadata
并通过遍历对象将其展开,就像它们在 Annotated
类型中已被“展开”一样。相同的逻辑应应用于 PEP 646 Unpack
类型,以便 Annotated[T, Field(...)]
、Annotated[T, Unpack[Field(...)]]
和 Annotated[T, *Field(...)]
都被一致对待。
消耗注解类型的库还应忽略来自展开 GroupedMetadata
的任何它们不认识的元数据,就像它们在 Annotated
本身中忽略不认识的元数据一样。
我们自己的 annotated_types.Interval
类是一个 GroupedMetadata
,它将自己展开到 Gt
、Lt
等,因此这不是一个抽象问题。同样,annotated_types.Len
是一个 GroupedMetadata
,它将自己展开到 MinLen
(可选)和 MaxLen
。
消耗元数据
我们打算不对元数据和约束的 如何 使用进行规定,而是提供一个示例,说明如何从类型注解中解析约束,请参阅我们在 test_main.py 中的实现。
实现者需要确定如何使用这些元数据。您可以使用这些元数据用于运行时类型检查、生成模式或生成示例数据,以及其他用例。
设计与历史
这个包是由 Pydantic 和 Hypothesis 的维护者在 PyCon 2022 的冲刺中设计的,旨在让最终用户尽可能容易地为运行时库提供更多信息的注解。
它是故意最小化的,遵循 PEP-593 允许下游有相当大的自主权来决定他们选择支持什么(如果有的话)。尽管如此,我们预计保持简单并仅涵盖最常见用例将使用户和维护者获得最佳体验。如果您想为您的类型获得更多约束,请遵循我们的做法,通过定义和记录它们来实现!
项目详情
下载文件
下载适用于您的平台的文件。如果您不确定选择哪个,请了解更多关于 安装包 的信息。
源分发
构建分发
annotated_types-0.7.0.tar.gz 的散列
算法 | 散列摘要 | |
---|---|---|
SHA256 | aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89 |
|
MD5 | 5c943b7c51b0b7dcadf46da4a99c84a1 |
|
BLAKE2b-256 | ee67531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5 |
annotated_types-0.7.0-py3-none-any.whl的哈希值
算法 | 散列摘要 | |
---|---|---|
SHA256 | 1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53 |
|
MD5 | b132aea373e91e5a46e3b5425c9970e2 |
|
BLAKE2b-256 | 78b66307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8 |