精确且灵活的类型提示构建块
项目描述
optype
精确且灵活的类型提示构建块。
安装
Optype可在PyPI上作为optype
使用
pip install optype
为了可选的NumPy支持,建议使用numpy
扩展。这确保安装的numpy
版本与optype
兼容,遵循NEP 29和SPEC 0。
pip install "optype[numpy]"
有关更多信息,请参阅optype.numpy
文档。
示例
假设您正在编写一个twice(x)
函数,该函数评估2 * x
。实现它很简单,但类型注解怎么办?
因为 twice(2) == 4
,twice(3.14) == 6.28
以及 twice('I') = 'II'
,所以看起来把它写成 twice[T](x: T) -> T: ...
是一个好主意。然而,这样并不能包括像 twice(True) == 2
或 twice((42, True)) == (42, True, 42, True)
这样的情况,其中输入和输出类型是不同的。此外,twice
应该接受任何带有接受 2
作为参数的 __rmul__
方法的自定义类型。
这就是 optype
有用的地方,它为所有内置的特殊方法提供单方法协议。对于 twice
,我们可以使用 optype.CanRMul[T, R]
,正如名称所示,这是一个只有 def __rmul__(self, lhs: T) -> R: ...
方法的协议。有了这个,twice
函数可以这样编写
Python 3.10 | Python 3.12+ |
---|---|
from typing import Literal
from typing import TypeAlias, TypeVar
from optype import CanRMul
R = TypeVar('R')
Two: TypeAlias = Literal[2]
RMul2: TypeAlias = CanRMul[Two, R]
def twice(x: RMul2[R]) -> R:
return 2 * x
|
from typing import Literal
from optype import CanRMul
type Two = Literal[2]
type RMul2[R] = CanRMul[Two, R]
def twice[R](x: RMul2[R]) -> R:
return 2 * x
|
但是,对于实现 __add__
但不实现 __radd__
的类型怎么办?在这种情况下,我们可以返回 x * 2
作为后备方案(假设交换律)。因为 optype.Can*
协议是运行时可检查的,所以修订后的 twice2
函数可以紧凑地写成
Python 3.10 | Python 3.12+ |
---|---|
from optype import CanMul
Mul2: TypeAlias = CanMul[Two, R]
CMul2: TypeAlias = Mul2[R] | RMul2[R]
def twice2(x: CMul2[R]) -> R:
if isinstance(x, CanRMul):
return 2 * x
else:
return x * 2
|
from optype import CanMul
type Mul2[R] = CanMul[Two, R]
type CMul2[R] = Mul2[R] | RMul2[R]
def twice2[R](x: CMul2[R]) -> R:
if isinstance(x, CanRMul):
return 2 * x
else:
return x * 2
|
请参阅 examples/twice.py
以获取完整示例。
参考
optype
的 API 是平的;只需要一个 import optype as opt
就可以了(除了 optype.numpy
)。
optype
optype.copy
optype.dataclasses
optype.inspect
optype.json
optype.pickle
optype.string
optype.typing
optype.numpy
optype
在 optype
中有四种类型的事物存在
optype.Can{}
类型描述了可以对其执行的操作。例如,任何CanAbs[T]
类型都可以用作具有返回类型T
的内置函数abs()
的参数。大多数Can{}
实现了一个特殊方法,其名称直接与类型匹配。例如,CanAbs
实现了__abs__
,CanAdd
实现了__add__
等。optype.Has{}
是Can{}
的类似物,但用于特殊属性。例如,HasName
有一个__name__
属性,HasDict
有一个__dict__
属性等。optype.Does{}
描述了操作符的类型。因此,DoesAbs
是内置函数abs({})
的类型,而DoesPos
是前缀操作符+{}
的类型。optype.do_{}
是Does{}
的正确类型实现。对于每个do_{}
,都有一个Does{}
,反之亦然。因此,do_abs: DoesAbs
是abs({})
的类型别名,而do_pos: DoesPos
是operator.pos
的类型化版本。与operators
相比,optype.do_
操作符更完整,具有可运行时访问的类型注释,并且有你不必记住的名字。
参考文档的结构如下
这里所有 typing 协议 都位于根 optype
命名空间中。它们是 运行时可检查的,因此你可以执行例如 isinstance('snail', optype.CanAdd)
,如果你想要检查 snail
是否实现了 __add__
。
与collections.abc
不同,optype
的协议不是抽象基类,也就是说它们不扩展abc.ABC
,仅扩展typing.Protocol
。这允许optype
协议作为.pyi
类型存根的构建块。
内置类型转换
这些特殊方法的返回类型是不变的。如果返回其他(子)类型,Python将引发错误。这就是为什么这些optype
接口不接受泛型类型参数。
operator | 操作数 | |||
---|---|---|---|---|
表达式 | 函数 | 类型 | 方法 | 类型 |
complex(_) |
do_complex |
DoesComplex |
__complex__ |
CanComplex |
float(_) |
do_float |
DoesFloat |
__float__ |
CanFloat |
int(_) |
do_int |
DoesInt |
__int__ |
CanInt[R: int = int] |
bool(_) |
do_bool |
DoesBool |
__bool__ |
CanBool[R: bool = bool] |
bytes(_) |
do_bytes |
DoesBytes |
__bytes__ |
CanBytes[R: bytes = bytes] |
str(_) |
do_str |
DoesStr |
__str__ |
CanStr[R: str = str] |
[!NOTE] 可以用作
typing.Literal
的类型,其Can*
接口接受一个可选的类型参数R
。这可以用来指明字面量返回类型,以进行精确的打字,例如None
,True
和42
分别是CanBool[Literal[False]]
,CanInt[Literal[1]]
和CanStr[Literal['42']]
的实例。
这些格式化方法可以返回子类型为内置的str
的实例。同样适用于__format__
参数。所以如果你是一个想黑入Python的f-strings的10x开发者,但只有当你类型提示准确无误时;optype
是你的朋友。
operator | 操作数 | |||
---|---|---|---|---|
表达式 | 函数 | 类型 | 方法 | 类型 |
repr(_) |
do_repr |
DoesRepr |
__repr__ |
CanRepr[R: str = str] |
format(_, x) |
do_format |
DoesFormat |
__format__ |
CanFormat[T: str = str, R: str = str] |
此外,optype
为具有(自定义)哈希或索引方法的类型提供了协议
operator | 操作数 | |||
---|---|---|---|---|
表达式 | 函数 | 类型 | 方法 | 类型 |
hash(_) |
do_hash |
DoesHash |
__hash__ |
CanHash |
_.__index__() (文档) |
do_index |
DoesIndex |
__index__ |
CanIndex[R: int = int] |
丰富关系
“丰富”的比较特殊方法通常返回一个bool
。然而,可以返回任何类型的实例(例如numpy数组)。这就是为什么相应的optype.Can*
接口接受一个返回类型的第二个类型参数,当省略时默认为bool
。第一个类型参数与传递的方法参数匹配,即右侧操作数,在此表示为x
。
operator | 操作数 | ||||
---|---|---|---|---|---|
表达式 | reflected | 函数 | 类型 | 方法 | 类型 |
_ == x |
x == _ |
do_eq |
DoesEq |
__eq__ |
CanEq[T = object, R = bool] |
_ != x |
x != _ |
do_ne |
DoesNe |
__ne__ |
CanNe[T = object, R = bool] |
_ < x |
x > _ |
do_lt |
DoesLt |
__lt__ |
CanLt[T, R = bool] |
_ <= x |
x >= _ |
do_le |
DoesLe |
__le__ |
CanLe[T, R = bool] |
_ > x |
x < _ |
do_gt |
DoesGt |
__gt__ |
CanGt[T, R = bool] |
_ >= x |
x <= _ |
do_ge |
DoesGe |
__ge__ |
CanGe[T, R = bool] |
二元运算
在Python文档中,这些被称为“算术运算”。但是操作数并不限于数值类型,并且由于这些操作不是必需的交换律,可能是非确定性的,并且可能有副作用。将它们归类为“算术”至少是一个小小的夸张。
operator | 操作数 | |||
---|---|---|---|---|
表达式 | 函数 | 类型 | 方法 | 类型 |
_ + x |
do_add |
DoesAdd |
__add__ |
CanAdd[T, R] |
_ - x |
do_sub |
DoesSub |
__sub__ |
CanSub[T, R] |
下划线 * x |
do_mul |
DoesMul |
__mul__ |
CanMul[T, R] |
下划线 @ x |
do_matmul |
DoesMatmul |
__matmul__ |
CanMatmul[T, R] |
下划线 / x |
do_truediv |
DoesTruediv |
__truediv__ |
CanTruediv[T, R] |
下划线 // x |
do_floordiv |
DoesFloordiv |
__floordiv__ |
CanFloordiv[T, R] |
下划线 % x |
do_mod |
DoesMod |
__mod__ |
CanMod[T, R] |
divmod(_, x) |
do_divmod |
DoesDivmod |
__divmod__ |
CanDivmod[T, R] |
下划线 ** x pow(_, x)
|
do_pow/2 |
DoesPow |
__pow__ |
CanPow2[T, R] CanPow[T, None, R, Never]
|
pow(_, x, m) |
do_pow/3 |
DoesPow |
__pow__ |
CanPow3[T, M, R] CanPow[T, M, Never, R]
|
下划线 << x |
do_lshift |
DoesLshift |
__lshift__ |
CanLshift[T, R] |
下划线 >> x |
do_rshift |
DoesRshift |
__rshift__ |
CanRshift[T, R] |
下划线 & x |
do_and |
DoesAnd |
__and__ |
CanAnd[T, R] |
下划线 ^ x |
do_xor |
DoesXor |
__xor__ |
CanXor[T, R] |
下划线 | x |
do_or |
DoesOr |
__or__ |
CanOr[T, R] |
![NOTE] 因为
pow()
可以接受一个可选的第三个参数,optype
为具有两个和三个参数的pow()
提供了单独的接口。此外,还有重载的交集类型CanPow[T, M, R, RM] =: CanPow2[T, R] & CanPow3[T, M, RM]
,作为可以接受可选第三个参数的类型的接口。
反射操作
对于上面的二元前缀运算符,optype
还提供了具有 反射(交换)操作数的接口,例如 __radd__
是 __add__
的反射。它们的命名与原始的相同,但前面有 CanR
前缀,即 __name__.replace('Can', 'CanR')
。
operator | 操作数 | |||
---|---|---|---|---|
表达式 | 函数 | 类型 | 方法 | 类型 |
x + _ |
do_radd |
DoesRAdd |
__radd__ |
CanRAdd[T, R] |
x - _ |
do_rsub |
DoesRSub |
__rsub__ |
CanRSub[T, R] |
x * _ |
do_rmul |
DoesRMul |
__rmul__ |
CanRMul[T, R] |
x @ _ |
do_rmatmul |
DoesRMatmul |
__rmatmul__ |
CanRMatmul[T, R] |
x / _ |
do_rtruediv |
DoesRTruediv |
__rtruediv__ |
CanRTruediv[T, R] |
x // _ |
do_rfloordiv |
DoesRFloordiv |
__rfloordiv__ |
CanRFloordiv[T, R] |
x % _ |
do_rmod |
DoesRMod |
__rmod__ |
CanRMod[T, R] |
divmod(x, _) |
do_rdivmod |
DoesRDivmod |
__rdivmod__ |
CanRDivmod[T, R] |
x ** _ pow(x, _)
|
do_rpow |
DoesRPow |
__rpow__ |
CanRPow[T, R] |
x << _ |
do_rlshift |
DoesRLshift |
__rlshift__ |
CanRLshift[T, R] |
x >> _ |
do_rrshift |
DoesRRshift |
__rrshift__ |
CanRRshift[T, R] |
x & _ |
do_rand |
DoesRAnd |
__rand__ |
CanRAnd[T, R] |
x ^ _ |
do_rxor |
DoesRXor |
__rxor__ |
CanRXor[T, R] |
x | _ |
do_ror |
DoesROr |
__ror__ |
CanROr[T, R] |
![NOTE]
CanRPow
对应于CanPow2
;3参数的“模”pow
在 Python 中不会反射。根据相关的 python 文档
注意三参数的
pow()
不会尝试调用__rpow__()
(强制转换规则将变得过于复杂)。
就地操作
类似于反射操作,原地/增强操作前缀为 CanI
,即
operator | 操作数 | |||
---|---|---|---|---|
表达式 | 函数 | 类型 | 方法 | 类型 |
下划线 += x |
do_iadd |
DoesIAdd |
__iadd__ |
CanIAdd[T, R] CanIAddSelf[T]
|
下划线 -= x |
do_isub |
DoesISub |
__isub__ |
CanISub[T, R] CanISubSelf[T]
|
下划线 *= x |
do_imul |
DoesIMul |
__imul__ |
CanIMul[T, R] CanIMulSelf[T]
|
下划线 @= x |
do_imatmul |
是否支持IMatmul |
__imatmul__ |
是否支持IMatmul[T, R] 是否支持IMatmulSelf[T]
|
_ /= x |
执行do_itruediv |
是否支持ITruediv |
__itruediv__ |
是否支持ITruediv[T, R] 是否支持ITruedivSelf[T]
|
_ //= x |
执行do_ifloordiv |
是否支持IFloordiv |
__ifloordiv__ |
是否支持IFloordiv[T, R] 是否支持IFloordivSelf[T]
|
_ %= x |
执行do_imod |
是否支持IMod |
__imod__ |
是否支持IMod[T, R] 是否支持IModSelf[T]
|
_ **= x |
执行do_ipow |
是否支持IPow |
__ipow__ |
是否支持IPow[T, R] 是否支持IPowSelf[T]
|
_ <<= x |
执行do_ilshift |
是否支持ILshift |
__ilshift__ |
是否支持ILshift[T, R] 是否支持ILshiftSelf[T]
|
_ >>= x |
执行do_irshift |
是否支持IRshift |
__irshift__ |
是否支持IRshift[T, R] 是否支持IRshiftSelf[T]
|
_ &= x |
执行do_iand |
是否支持IAnd |
__iand__ |
是否支持IAnd[T, R] 是否支持IAndSelf[T]
|
_ ^= x |
执行do_ixor |
是否支持IXor |
__ixor__ |
是否支持IXor[T, R] 是否支持IXorSelf[T]
|
_ |= x |
执行do_ior |
是否支持IOr |
__ior__ |
是否支持IOr[T, R] 是否支持IOrSelf[T]
|
这些原地操作符通常在执行一些原地修改后返回自身。但不幸的是,目前无法使用Self
来实现这一点(即不允许像type MyAlias[T] = optype.CanIAdd[T, Self]
这样的用法)。因此,为了减轻这种难以忍受的痛苦,optype
为您提供了现成的别名。它们的名称相同,但额外添加了*Self
后缀,例如optype.CanIAddSelf[T]
。
一元运算
operator | 操作数 | |||
---|---|---|---|---|
表达式 | 函数 | 类型 | 方法 | 类型 |
+_ |
执行do_pos |
是否支持Pos |
__pos__ |
是否支持Pos[R] 是否支持PosSelf
|
-_ |
执行do_neg |
是否支持Neg |
__neg__ |
是否支持Neg[R] 是否支持NegSelf
|
~_ |
执行do_invert |
是否支持Invert |
__invert__ |
是否支持Invert[R] 是否支持InvertSelf
|
abs(_) |
执行do_abs |
是否支持Abs |
__abs__ |
是否支持Abs[R] 是否支持AbsSelf
|
舍入
round()
内置函数接受一个可选的第二个参数。从类型的角度来看,round()
有两个重载版本,一个参数为一个,另一个参数为两个。对于这两个重载版本,optype
提供了独立的操作数接口:CanRound1[R]
和CanRound2[T, RT]
。此外,optype
还提供了它们的(重载的)交集类型:CanRound[T, R, RT] = CanRound1[R] & CanRound2[T, RT]
。
operator | 操作数 | |||
---|---|---|---|---|
表达式 | 函数 | 类型 | 方法 | 类型 |
round(_) |
执行do_round/1 |
是否支持Round |
__round__/1 |
CanRound1[T = int] |
round(_, n) |
执行do_round/2 |
是否支持Round |
__round__/2 |
CanRound2[T = int, RT = float] |
round(_, n=...) |
执行do_round |
是否支持Round |
__round__ |
CanRound[T = int, R = int, RT = float] |
例如,类型检查器会将以下代码标记为有效(在严格模式下使用pyright进行测试)
x: float = 3.14
x1: CanRound1[int] = x
x2: CanRound2[int, float] = x
x3: CanRound[int, int, float] = x
此外,还有来自math
标准库的替代舍入函数
operator | 操作数 | |||
---|---|---|---|---|
表达式 | 函数 | 类型 | 方法 | 类型 |
math.trunc(_) |
执行do_trunc |
是否支持Trunc |
__trunc__ |
CanTrunc[R = int] |
math.floor(_) |
执行do_floor |
是否支持Floor |
__floor__ |
CanFloor[R = int] |
math.ceil(_) |
执行do_ceil |
是否支持Ceil |
__ceil__ |
CanCeil[R = int] |
几乎所有实现都使用int
作为R
。实际上,如果没有指定R
的类型,它将默认为int
。但从技术上讲,这些方法可以返回任何内容。
可调用对象
与operator
不同,optype
为可调用对象提供了操作符:optype.do_call(f, *args. **kwargs)
。
CanCall
类似于collections.abc.Callable
,但它可以在运行时进行检查,并且不使用晦涩的技巧。
operator | 操作数 | |||
---|---|---|---|---|
表达式 | 函数 | 类型 | 方法 | 类型 |
_(*args, **kwargs) |
执行do_call |
是否支持Call |
__call__ |
CanCall[**Pss, R] |
[!注意] Pyright(以及可能的其他类型检查器)通常比
optype.CanCall
在更多的地方接受collections.abc.Callable
。这可能与其对typing.ParamSpec
的协/逆变指定不足有关(它们几乎总是应该是逆变的,但当前它们只能是不变的)。如果你遇到这种情况,请就此事提交一个问题,以便我们进一步调查。
迭代
Python 中 iter(_)
的操作数 x
被称为 可迭代对象,这是 collections.abc.Iterable[V]
常用的用途(例如作为基类,或用于实例检查)。
optype
类似物是 CanIter[R]
,正如其名称所示,它也实现了 __iter__
。但与 Iterable[V]
不同,其类型参数 R
绑定到 iter(_) -> R
的返回类型。这使得能够注释 iter(_)
返回的 可迭代对象 的具体类型。仅能注释迭代值的类型。Iterable[V]
。要了解为什么这不可能,请参阅python/typing#548。
collections.abc.Iterator[V]
更加奇怪;它是 Iterable[V]
的子类型。对于那些熟悉 collections.abc
的人来说,这可能是个惊喜,但迭代器只需要实现 __next__
,不需要 __iter__
。这意味着 Iterator[V]
是不必要地限制性的。除了在理论上是“丑陋的”之外,它还有重大的性能影响,因为 isinstance
在 typing.Protocol
上的时间复杂度是 $O(n)$,其中 $n$ 指的是成员的数量。所以即使忽略继承和 abc.ABC
的使用开销,collections.abc.Iterator
仍然比它需要的慢两倍。
这就是为什么 optype.CanNext[V]
和 optype.CanNext[V]
是比 abracadabra 集合中的 Iterable
和 Iterator
更好的替代品的原因之一。它们的定义如下
operator | 操作数 | |||
---|---|---|---|---|
表达式 | 函数 | 类型 | 方法 | 类型 |
next(_) |
do_next |
DoesNext |
__next__ |
CanNext[V] |
iter(_) |
do_iter |
DoesIter |
__iter__ |
CanIter[R: CanNext[object]] |
为了与 collections.abc
兼容,存在 optype.CanIterSelf[V]
,这是一个协议,其 __iter__
返回 typing.Self
,以及一个返回 T
的 __next__
方法。即它等同于 collections.abc.Iterator[V]
,但没有 abc
的无聊之处。
等待对象
optype
几乎与 collections.abc.Awaitable[R]
相同,只是 optype.CanAwait[R]
是一个纯接口,而 Awaitable
也是一个抽象基类(这使得在编写存根时完全无用地存在)。
operator | 操作数 | |
---|---|---|
表达式 | 方法 | 类型 |
await _ |
__await__ |
CanAwait[R] |
异步迭代
是的,你猜对了;abracadabra 集合为异步可迭代对象(或者说是“iteramblers”?)犯了完全相同的错误。
但别担心;这里就是 optype
的替代品
operator | 操作数 | |||
---|---|---|---|---|
表达式 | 函数 | 类型 | 方法 | 类型 |
anext(_) |
do_anext |
DoesANext |
__anext__ |
CanANext[V] |
aiter(_) |
do_aiter |
DoesAIter |
__aiter__ |
CanAIter[R: CanAnext[object]] |
但是等等,V
不应该是 CanAwait
吗?好吧,只有当你不想被炒鱿鱼时……从技术上来说,__anext__
可以返回任何类型,而 anext
会传递它而不纠缠(实例检查很慢,现在别再烦我了)。有关详细信息,请参阅python/typeshed#7491。尽管某事是合法的,但这并不意味着它是个好主意(别吃黄色的雪)。
此外,还有 optype.CanAIterSelf[R]
,它同时具有 __aiter__() -> Self
和 __anext__() -> V
方法。
容器
operator | 操作数 | |||
---|---|---|---|---|
表达式 | 函数 | 类型 | 方法 | 类型 |
len(_) |
do_len |
DoesLen |
__len__ |
CanLen[R: int = int] |
_.__length_hint__() (文档) |
do_length_hint |
DoesLengthHint |
__length_hint__ |
CanLengthHint[R: int = int] |
_[k] |
do_getitem |
支持Getitem |
__getitem__ |
支持Getitem[K, V] |
_.__missing__() (文档) |
do_missing |
支持Missing |
__missing__ |
支持Missing[K, D] |
_[k] = v |
do_setitem |
支持Setitem |
__setitem__ |
支持Setitem[K, V] |
del _[k] |
do_delitem |
支持Delitem |
__delitem__ |
支持Delitem[K] |
k in _ |
do_contains |
支持Contains |
__contains__ |
支持Contains[K = object] |
reversed(_) |
do_reversed |
支持Reversed |
__reversed__ |
支持Reversed[R] ,或者支持Sequence[I, V, N = int]
|
由于支持Missing[K, D]
通常在没有支持Getitem[K, V]
的情况下不会显示,因此optype
方便地将它们组合为optype.CanGetMissing[K, V, D=V]
。
类似地,存在optype.CanSequence[K: CanIndex | slice, V]
,这是支持Len
和支持Item[I, V]
的组合,充当更具体和灵活的collections.abc.Sequence[V]
。
属性
operator | 操作数 | |||
---|---|---|---|---|
表达式 | 函数 | 类型 | 方法 | 类型 |
v = _.k 或者v = getattr(_, k)
|
do_getattr |
支持Getattr |
__getattr__ |
支持Getattr[K: str = str, V = object] |
_.k = v 或者setattr(_, k, v)
|
do_setattr |
支持Setattr |
__setattr__ |
支持Setattr[K: str = str, V = object] |
del _.k 或者delattr(_, k)
|
do_delattr |
支持Delattr |
__delattr__ |
支持Delattr[K: str = str] |
dir(_) |
do_dir |
支持Dir |
__dir__ |
支持Dir[R: CanIter[CanIterSelf[str]]] |
上下文管理器
支持with
语句。
operator | 操作数 | |
---|---|---|
表达式 | 方法 | 类型 |
__enter__ |
支持Enter[C] ,或者支持EnterSelf |
|
__exit__ |
支持Exit[R = None]
|
|
with _ as c |
__enter__ ,以及__exit__
|
支持With[C, R=None] ,或者支持WithSelf[R=None]
|
支持EnterSelf
和支持WithSelf
分别是支持Enter[Self]
和支持With[Self, R]
的(运行时可检查)别名。
对于async with
语句,接口看起来非常相似
operator | 操作数 | |
---|---|---|
表达式 | 方法 | 类型 |
__aenter__ |
支持AEnter[C] ,或者支持AEnterSelf
|
|
__aexit__ |
支持AExit[R=None] |
|
async with _ as c |
__aenter__ ,以及__aexit__
|
支持AsyncWith[C, R=None] ,或者支持AsyncWithSelf[R=None]
|
描述符
描述符的接口。
operator | 操作数 | |
---|---|---|
表达式 | 方法 | 类型 |
v: V = T().d vt: VT = T.d
|
__get__ |
支持Get[T: object, V, VT = V] |
T().k = v |
__set__ |
支持Set[T: object, V] |
del T().k |
__delete__ |
支持Delete[T: object] |
class T: d = _ |
__set_name__ |
支持SetName[T: object, N: str = str] |
缓冲区类型
使用缓冲协议模拟缓冲类型接口。
operator | 操作数 | |
---|---|---|
表达式 | 方法 | 类型 |
v = memoryview(_) |
__buffer__ |
支持Buffer[T: int = int] |
del v |
__release_buffer__ |
支持ReleaseBuffer |
optype.copy
对于标准库copy
,optype.copy
提供以下运行时检查接口
copy 标准库 |
optype.copy |
|
---|---|---|
函数 | 类型 | 方法 |
copy.copy(_) -> R |
__copy__() -> R |
支持Copy[R] |
copy.deepcopy(_, memo={}) -> R |
__deepcopy__(memo, /) -> R |
支持Deepcopy[R] |
copy.replace(_, /, **changes: V) -> R [1] |
__replace__(**changes: V) -> R |
支持Replace[V, R] |
[1] copy.replace
需要python>=3.13
(但optype.copy.CanReplace
不需要)
在实际应用中,实例的副本与原始实例相同类型是有意义的。但由于 typing.Self
不能用作类型参数,这导致难以正确地定义类型。相反,您可以使用 optype.copy.Can{}Self
类型,这些类型是以下(递归)类型别名的运行时检查等价物。
type CanCopySelf = CanCopy[CanCopySelf]
type CanDeepcopySelf = CanDeepcopy[CanDeepcopySelf]
type CanReplaceSelf[V] = CanReplace[V, CanReplaceSelf[V]]
optype.dataclasses
对于标准库中的 dataclasses
,optype.dataclasses
提供了 HasDataclassFields[V: Mapping[str, Field]]
接口。它可以方便地用来检查一个类型或实例是否为数据类,即 isinstance(obj, HasDataclassFields)
。
optype.inspect
一组用于运行时检查类型、模块和其他对象的函数。
函数 | 描述 |
---|---|
|
是
返回一个类型参数或参数的 为了说明 >>> from typing import Literal, TypeAlias, get_args
>>> Falsy: TypeAlias = Literal[None] | Literal[False, 0] | Literal['', b'']
>>> get_args(Falsy)
(typing.Literal[None], typing.Literal[False, 0], typing.Literal['', b''])
但这与 官方 typing 文档 直接矛盾
因此,应该使用 >>> import optype as opt
>>> opt.inspect.get_args(Falsy)
(None, False, 0, '', b'')
>>> import typing
>>> import optype as opt
>>> type StringLike = str | bytes
>>> typing.get_args(StringLike)
()
>>> opt.inspect.get_args(StringLike)
(<class 'str'>, <class 'bytes'>)
很明显, |
|
是
返回一个包含成员名称的 |
|
返回一个包含传递的模块中公共协议的 |
|
检查对象是否可以迭代,即它是否可以用于 |
|
检查类型、方法 / 类方法 / 静态方法 / 属性是否被装饰为 请注意,除非将 |
|
Python 3.13 中新增的 |
is_runtime_protocol(_) |
检查类型表达式是否为 运行时协议,即带有 |
is_union_type(_) |
检查类型是否为 与 |
is_generic_alias(_) |
检查类型是否为 索引 类型,例如 与 虽然技术上 |
[!NOTE] 所有
optype.inspect
中的函数也适用于 Python 3.12 的type _
别名(即types.TypeAliasType
)以及typing.Annotated
。
optype.json
json
标准库的类型别名
Value |
AnyValue |
json.load(s) 返回类型 |
json.dumps(s) 输入类型 |
---|---|
Array[V: Value = Value] |
AnyArray[V: AnyValue = AnyValue] |
Object[V: Value = Value] |
AnyObject[V: AnyValue = AnyValue] |
(Any)Value
可以是任何 JSON 输入,即 Value | Array | Object
等同于 Value
。还值得注意的是,Value
是 AnyValue
的子类型,这意味着 AnyValue | Value
等同于 AnyValue
。
optype.pickle
对于 pickle
标准库,optype.pickle
提供以下接口
方法 | signature (bound) | 类型 |
---|---|---|
__reduce__ |
() -> R |
CanReduce[R: str | tuple = ...] |
__reduce_ex__ |
(CanIndex) -> R |
CanReduceEx[R: str | tuple = ...] |
__getstate__ |
() -> S |
CanGetstate[S] |
__setstate__ |
(S) -> None |
CanSetstate[S] |
__getnewargs__ __new__
|
() -> tuple[V, ...] (V) -> Self |
CanGetnewargs[V] |
__getnewargs_ex__ __new__
|
() -> tuple[tuple[V, ...], dict[str, KV]] (*tuple[V, ...], **dict[str, KV]) -> Self |
CanGetnewargsEx[V, KV] |
optype.string
string
标准库包含实用的常量,但它有两个问题
- 常量包含一组字符,但表示为单个字符串。这使得为单个字符进行类型提示实际上变得不可能,因此 typeshed 目前将这些常量类型化为
LiteralString
。 - 常量名称不统一,且不符合 PEP 8。
因此,optype.string
提供了一个替代接口,与 string
兼容,但略有不同
- 对于每个常量,都有一个对应的
Literal
类型别名用于表示单个字符。其名称与常量名称相同,但使用单数形式而不是复数形式。 - 与单个字符串不同,
optype.string
使用字符的tuple
,这样每个字符都有自己的typing.Literal
注解。请注意,这仅在 (based)pyright / pylance 中进行了测试,因此可能不与 mypy 一起使用(它比代码行数有更多的错误)。 - 常量的名称与 PEP 8 一致,并使用后缀表示变体,例如
DIGITS_HEX
而不是hexdigits
。 - 与
string
不同,optype.string
为二进制数字'0'
和'1'
有一个常量(和类型别名)DIGITS_BIN
(和DigitBin
)。因为除了builtins
中的oct
和hex
函数之外,还有builtins.bin
函数。
string._ |
optype.string._ |
||
---|---|---|---|
常量 | 字符类型 | 常量 | 字符类型 |
缺失 | DIGITS_BIN |
DigitBin |
|
octdigits |
LiteralString |
DIGITS_OCT |
DigitOct |
digits |
DIGITS |
Digit |
|
hexdigits |
DIGITS_HEX |
DigitHex |
|
ascii_letters |
LETTERS |
Letter |
|
ascii_lowercase |
LETTERS_LOWER |
LetterLower |
|
ascii_uppercase |
LETTERS_UPPER |
LetterUpper |
|
punctuation |
PUNCTUATION |
Punctuation |
|
whitespace |
WHITESPACE |
Whitespace |
|
printable |
PRINTABLE |
Printable |
每个 optype.string
常量都与相应的 string
常量完全相同(在连接/拆分之后),例如
>>> import string
>>> import optype as opt
>>> ''.join(opt.string.PRINTABLE) == string.printable
True
>>> tuple(string.printable) == opt.string.PRINTABLE
True
同样,常量 Literal
类型中的值与常量的值完全匹配
>>> import optype as opt
>>> from optype.inspect import get_args
>>> get_args(opt.string.Printable) == opt.string.PRINTABLE
True
optype.inspect.get_args
是 typing.get_args
的一个无故障变体,它正确地扁平化嵌套字面量、类型联合和 PEP 695 类型别名,以匹配官方类型规范。换句话说,typing.get_args
是另一个基本有缺陷的 python-typing 功能,在您最需要它的情况下毫无用处。
optype.typing
Any*
类型别名
为可以始终传递给 int
、float
、complex
、iter
或 typing.Literal
的任何内容提供类型别名
Python 构造函数 | optype.typing 别名 |
---|---|
int(_) |
AnyInt |
float(_) |
AnyFloat |
complex(_) |
AnyComplex |
iter(_) |
AnyIterable |
typing.Literal[_] |
AnyLiteral |
[!NOTE] 即使 某些
str
和bytes
可以转换为int
、float
、complex
,但大多数不能,因此不包括在这些类型别名中。
Empty*
类型别名
这些是空内置类型或集合,即长度为 0 或不产生元素的集合。
实例 | optype.typing 类型 |
---|---|
'' |
EmptyString |
b'' |
EmptyBytes |
() |
EmptyTuple |
[] |
EmptyList |
{} |
EmptyDict |
set() |
EmptySet |
(i for i in range(0)) |
EmptyIterable |
文字类型
字面量值 | optype.typing 类型 |
注释 |
---|---|---|
{False, True} |
LiteralFalse |
类似于 typing.LiteralString ,但用于 bool 。 |
{0, 1, ..., 255} |
LiteralByte |
范围在 0-255 之间的整数,组成 bytes 或 bytearray 对象。 |
optype.numpy
Optype 支持 NumPy 1 和 2。当前支持的最小版本是 1.24
,遵循 NEP 29 和 SPEC 0。
当使用 optype.numpy
时,建议使用带有 numpy
附加组件安装 optype
,以确保版本兼容性
pip install "optype[numpy]"
[!NOTE] 对于
optype.numpy
文档的其余部分,假设以下导入别名可用。from typing import Any, Literal import numpy as np import numpy.typing as npt import optype.numpy as onp
数组
Optype为np.ndarray
提供了通用的onp.Array
类型别名。它类似于npt.NDArray
,但包含两个(可选)类型参数:一个匹配形状类型(ND: tuple[int, ...]
),另一个匹配标量类型(ST: np.generic
)。
将npt.NDArray
和onp.Array
的定义并排放置时,它们之间的差异变得明显。
numpy.typing.NDArray
|
optype.numpy.Array
|
---|---|
type NDArray[
# no shape type
ST: np.generic, # no default
] = np.ndarray[Any, np.dtype[ST]]
|
type Array[
ND: tuple[int, ...] = tuple[int, ...],
ST: np.generic = np.generic,
] = np.ndarray[ND, np.dtype[ST]]
|
[!IMPORTANT] 目前,
np.ndarray
的形状类型参数(ND
)被定义为不可变的。这是不正确的:它应该是协变的。这意味着
ND: tuple[int, ...]
在onp.Array
中也是不可变的。结果是,例如,
def span(a: onp.Array[tuple[int], ST])
将不接受onp.Array[tuple[Literal[42]]]
作为参数,尽管Literal[42]
是int
的子类型。有关详细信息,请参阅numpy/numpy#25729和numpy/numpy#26081。
实际上,`onp.Array`几乎是`npt.NDArray`的泛化。
这是因为可以用onp.Array
完全定义npt.NDArray
(但反之则不行)。
type NDArray[ST: np.generic] = onp.Array[Any, ST]
有了onp.Array
,就可以对数组形状进行类型注解。
[!NOTE] 据消息人士透露,
onp.Array
可能会在不久的将来被回滚到NumPy中。
UFunc
Numpy的大部分公共API由通用函数组成,通常表示为ufuncs,它们是np.ufunc
的(可调用)实例。
[!TIP] 可以使用
np.frompyfunc
创建自定义ufuncs,也可以通过实现所需属性和方法(即鸭子类型)的用户定义类来创建。
但是,np.ufunc
有一个大问题;它不接受类型参数。这使得正确注释其可调用签名及其文字属性(例如.nin
和.identity
)变得非常困难。
这就是optype.numpy.UFunc
发挥作用的地方:它是一个运行时可检查的泛型类型协议,已经经过彻底的类型和单元测试,以确保与NumPy的所有ufunc定义兼容。它的泛型类型签名大致如下
UFunc[
# The type of the (bound) `__call__` method.
Fn: CanCall = CanCall,
# The types of the `nin` and `nout` (readonly) attributes.
# Within numpy these match either `Literal[1]` or `Literal[2]`.
Nin: int = int,
Nout: int = int,
# The type of the `signature` (readonly) attribute;
# Must be `None` unless this is a generalized ufunc (gufunc), e.g.
# `np.matmul`.
Sig: str | None = str | None,
# The type of the `identity` (readonly) attribute (used in `.reduce`).
# Unless `Nin: Literal[2]`, `Nout: Literal[1]`, and `Sig: None`,
# this should always be `None`.
# Note that `complex` also includes `bool | int | float`.
Id: complex | bytes | str | None = float | None,
]
[!NOTE] 不幸的是,
np.ufunc
的额外可调用方法(at
、reduce
、reduceat
、accumulate
和outer
)的注解错误(作为None
属性,尽管在运行时它们是调用时会引发ValueError
的方法)。这目前使得在optype.numpy.UFunc
中正确注解这些方法变得不可能;这样做将使其与NumPy的ufuncs不兼容。
形状类型别名
形状不过是(非负)整数的一个元组,即tuple[int, ...]
的一个实例,例如(42,)
、(6, 6, 6)
或()
。形状的长度通常被称为数组的维度数或维度。对于数组,这可以通过np.ndarray.ndim
访问,它是len(np.ndarray.shape)
的一个别名。
[!NOTE] 在NumPy 2之前,最大维度数是32,但后来已增加到64。
为了使对数组形状进行类型注解更容易,optype提供了两个形状类型别名家族:AtLeast{N}D
和AtMost{N}D
。{N}
应由维度数替换,目前限制为0
、1
、2
和3
。
这两个家族都是泛型的,它们的(可选)类型参数必须是int
(默认)或文字(非负)整数,即类似于typing.Literal[N: int]
。
[!NOTE] NumPy具有
shape
参数的函数通常也接受一个“基础”int
,它是tuple[int]
的缩写。但为了保持一致性,AtLeast{N}D
和AtMost{N}D
仅限于整数元组。
名称 AtLeast{N}D
和 AtMost{N}D
非常直观易懂
AtLeast{N}D
是一个tuple[int, ...]
,其ndim >= N
AtMost{N}D
是一个tuple[int, ...]
,其ndim <= N
形状别名的大致定义如下
AtLeast{N}D |
AtMost{N}D |
||
---|---|---|---|
类型签名 | 别名类型 | 类型签名 | 类型别名 |
type AtLeast0D[
Ds: int = int,
]
|
tuple[Ds, ...]
|
type AtMost0D
|
tuple[()]
|
type AtLeast1D[
D0: int = int,
Ds: int = int,
]
|
tuple[
D0,
*tuple[Ds, ...],
]
|
type AtMost1D[
D0: int = int,
]
|
tuple[D0] | AtMost0D
|
type AtLeast2D[
D0: int = int,
D1: int = int,
Ds: int = int,
]
|
tuple[
D0,
D1,
*tuple[Ds, ...],
]
|
type AtMost2D[
D0: int = int,
D1: int = int,
]
|
(
tuple[D0, D1]
| AtMost1D[D0]
)
|
type AtLeast3D[
D0: int = int,
D1: int = int,
D2: int = int,
Ds: int = int,
]
|
tuple[
D0,
D1,
D2,
*tuple[Ds, ...],
]
|
type AtMost3D[
D0: int = int,
D1: int = int,
D2: int = int,
]
|
(
tuple[D0, D1, D2]
| AtMost2D[D0, D1]
)
|
标量
optype.numpy.Scalar
接口是一个通用的运行时可检查协议,它可以从名称和类型的角度看作是“更具体”的 np.generic
。
它的类型签名大致如下
Scalar[
# The "Python type", so that `Scalar.item() -> PT`.
PT: object,
# The "N-bits" type (without having to deal with `npt.NBitBase`).
# It matches the `itemsize: NB` property.
NB: int = int,
]
它可以被用作,例如
are_birds_real: Scalar[bool, Literal[1]] = np.bool_(True)
the_answer: Scalar[int, Literal[2]] = np.uint16(42)
alpha: Scalar[float, Literal[8]] = np.float64(1 / 137)
[!NOTE] 对于
itemsize
的第二个类型参数可以省略,这等同于将其设置为int
,因此Scalar[PT]
和Scalar[PT, int]
是等效的。
DType
在 NumPy 中,一个 dtype(数据类型)对象是 numpy.dtype[ST: np.generic]
类型的实例。它通常用于传递标量的元数据,例如在数组中。
由于 np.dtype
的类型参数是可选的,因此使用别名 optype.numpy.DType
可能更方便,它定义为
type DType[ST: np.generic = np.generic] = np.dtype[ST]
除了“驼峰命名法”的名称外,与 np.dtype
的唯一区别是类型参数可以省略,在这种情况下,它等同于 np.dtype[np.generic]
,但更简洁。
Any*Array
和 Any*DType
Any{Scalar}Array
类型别名描述了当传递给 numpy.asarray
(或任何其他 numpy.ndarray
构造函数)时,结果为具有特定 dtype(即 numpy.dtypes.{Scalar}DType
)的所有内容。
[!NOTE]
numpy.dtypes
文档 自 NumPy 1.25 以来存在,但其类型注解在 NumPy 2.1 之前是错误的(参见 numpy/numpy#27008)
有关 NumPy 标量类型层次结构的更多信息,请参阅 文档。
抽象类型
numpy._ |
optype.numpy._ |
||
---|---|---|---|
标量类型 | 基本类型 | 数组类型 | dtype 类型 |
通用类型 |
AnyArray |
AnyDType |
|
数字 |
通用类型 |
AnyNumberArray |
AnyNumberDType |
整数 |
数字 |
AnyIntegerArray |
AnyIntegerDType |
不精确数字 |
AnyInexactArray |
AnyInexactDType |
|
无符号整数 |
整数 |
AnyUnsignedIntegerArray |
AnyUnsignedIntegerDType |
有符号整数 |
AnySignedIntegerArray |
AnySignedIntegerDType |
|
浮点数 |
不精确数字 |
AnyFloatingArray |
AnyFloatingDType |
复浮点数 |
AnyComplexFloatingArray |
AnyComplexFloatingDType |
无符号整数
numpy._
|
optype.numpy._
|
||
---|---|---|---|
标量类型 | 基本类型 | 数组类型 | dtype 类型 |
uint8 |
无符号整数 |
AnyUInt8Array |
AnyUInt8DType |
uint16 |
AnyUInt16Array |
AnyUInt16DType |
|
uint32 |
AnyUInt32Array |
AnyUInt32DType |
|
uint64 |
AnyUInt64Array |
AnyUInt64DType |
|
uintp |
AnyUIntPArray |
AnyUIntPDType |
|
ubyte |
AnyUByteArray |
AnyUByteDType |
|
ushort |
AnyUShortArray |
AnyUShortDType |
|
uintc |
AnyUIntCArray |
AnyUIntCDType |
|
ulong |
AnyULongArray |
AnyULongDType |
|
ulonglong |
AnyULongLongArray |
AnyULongLongDType |
有符号整数
numpy._
|
optype.numpy._
|
||
---|---|---|---|
标量类型 | 基本类型 | 数组类型 | dtype 类型 |
int8 |
有符号整数 |
AnyInt8Array |
AnyInt8DType |
int16 |
AnyInt16Array |
AnyInt16DType |
|
int32 |
AnyInt32Array |
AnyInt32DType |
|
int64 |
AnyInt64Array |
AnyInt64DType |
|
intp |
AnyIntPArray |
AnyIntPDType |
|
byte |
AnyByteArray |
AnyByteDType |
|
short |
AnyShortArray |
AnyShortDType |
|
intc |
AnyIntCArray |
AnyIntCDType |
|
long |
AnyLongArray |
AnyLongDType |
|
longlong |
AnyLongLongArray |
AnyLongLongDType |
浮点数
numpy._
|
optype.numpy._
|
||
---|---|---|---|
标量类型 | 基本类型 | 数组类型 | dtype 类型 |
float16 |
浮点数 |
AnyFloat16Array |
AnyFloat16DType |
half |
|||
float32 |
AnyFloat32Array |
AnyFloat32DType |
|
single |
|||
float64 |
AnyFloat64Array |
AnyFloat64DType |
|
double |
|||
longdouble |
AnyLongDoubleArray |
AnyLongDoubleDType |
复数
numpy._
|
optype.numpy._
|
||
---|---|---|---|
标量类型 | 基本类型 | 数组类型 | dtype 类型 |
complex64 |
复浮点数 |
AnyComplex64Array |
AnyComplex64DType |
csingle |
|||
complex128 |
AnyComplex128Array |
AnyComplex128DType |
|
cdouble |
|||
clongdouble |
AnyCLongDoubleArray |
AnyCLongDoubleDType |
"灵活的"
具有“灵活的”长度的标量类型,其值长度取决于特定的 np.dtype
实例化。
numpy._
|
optype.numpy._
|
||
---|---|---|---|
标量类型 | 基本类型 | 数组类型 | dtype 类型 |
str_ |
字符 |
AnyStrArray |
AnyStrDType |
bytes_ |
AnyBytesArray |
AnyBytesDType |
|
void |
flexible |
AnyVoidArray |
AnyVoidDType |
其他类型
numpy._
|
optype.numpy._
|
||
---|---|---|---|
标量类型 | 基本类型 | 数组类型 | dtype 类型 |
bool_ |
通用类型 |
AnyBoolArray |
AnyBoolDType |
datetime64 |
AnyDateTime64Array |
AnyDateTime64DType |
|
timedelta64 |
AnyTimeDelta64Array |
AnyTimeDelta64DType |
|
object_ |
AnyObjectArray |
AnyObjectDType |
|
缺失 | AnyStringArray |
AnyStringDType |
底层接口
在 optype.numpy
中有多个 Can*
(单方法)和 Has*
(单属性)协议,与 NumPy Python API 的 __array_*__
魔术方法相关。这些类型协议,就像 optype.Can*
和 optype.Has*
一样,是运行时可检查的且可扩展的(即不是 @final
)。
[!TIP] 这些协议的所有类型参数都可以省略,这相当于传递其上界类型。
协议类型签名 | 实现 | NumPy 文档 |
---|---|---|
class CanArray[
ND: tuple[int, ...] = ...,
ST: np.generic = ...,
]
|
def __array__[RT = ST](
_,
dtype: DType[RT] | None = ...,
) -> Array[ND, RT]
|
|
class CanArrayUFunc[
U: UFunc = ...,
R: object = ...,
]
|
def __array_ufunc__(
_,
ufunc: U,
method: LiteralString,
*args: object,
**kwargs: object,
) -> R
|
|
class CanArrayFunction[
F: CanCall[..., object] = ...,
R = object,
]
|
def __array_function__(
_,
func: F,
types: CanIterSelf[type[CanArrayFunction]],
args: tuple[object, ...],
kwargs: Mapping[str, object],
) -> R
|
|
class CanArrayFinalize[
T: object = ...,
]
|
def __array_finalize__(_, obj: T)
|
|
class CanArrayWrap
|
def __array_wrap__[ND, ST](
_,
array: Array[ND, ST],
context: (...) | None = ...,
return_scalar: bool = ...,
) -> Self | Array[ND, ST]
|
|
class HasArrayInterface[
V: Mapping[str, object] = ...,
]
|
__array_interface__: V
|
|
class HasArrayPriority
|
__array_priority__: float
|
|
class HasDType[
DT: DType = ...,
]
|
dtype: DT
|
项目详情
下载文件
下载适用于您平台的文件。如果您不确定要选择哪一个,请了解更多关于 安装包 的信息。