考虑以下代码:
from typing import Any, Callable, Coroutine
class Cache[**P, R]:
@classmethod
def decorate(cls, **params):
def decorator(f: Callable[P, Coroutine[Any, Any, R]]) -> Callable[P, Coroutine[Any, Any, R]]:
return f # in the real world, we instantiate a Cache here
return decorator
@Cache.decorate()
async def some_function(i: int) -> int:
return i + 1
cached_function = Cache.decorate()(some_function)
如果我询问 pyright 包装器Cache.decorate
之前的类型(检查上面代码中的@classmethod
单词),它会返回:decorate
(method) def decorate(
cls: type[Self@Cache[P@Cache, R@Cache]],
**params: Unknown
) -> ((f: ((**P@Cache) -> (Coroutine[Any, Any, R@Cache])) -> ((**P@Cache) -> Coroutine[Any, Any, R@Cache]))
在我看来,它理解P
(参数类型)和R
(返回类型)是否正确连接。
Cache.decorate
但是,如果我要求它在用作装饰器的上下文中进行自省,它会返回:
(method) def decorate(**params: Unknown) -> ((f: ((...) -> Coroutine[Any, Any, Unknown])) -> ((...) -> Coroutine[Any, Any, Unknown]))
...也就是说,输入类型和输出类型之间的关系已被完全丢弃!
decorator
取决于两个上下文类型变量:P
和R
。在类的上下文中Cache
,这些是未知的,但在调用时假定 Pyright 知道它们。然而,当
Cache.decorate()
调用时,Pyright 没有获得足够的信息来解析P
和R
(没有显式类型参数和没有参数),因此这两个被解析为Unknown
。一个简单的解决方法是明确参数化
Cache
:(操场)
然而,这并不能找到问题的根源,即您没有正确地指定您想要的东西。
Cache
是泛型的P
,R
因此必须存在一种方法,Pyright 可以推断出与这些参数相对应的类型。通常,这些是通过将参数传递给创建该类的函数来处理的,通常是__new__
/__init__
和 factory@classmethod
。decorate()
是一种工厂方法,但它不会Cache
自行创建 的实例。它需要一些参数,但这些参数不会影响被装饰的类型some_function()
或要创建的 的类型Cache
。相反,
decorate()
旨在创建装饰器,而装饰器本身会创建 的实例Cache
。您想要参数化的就是这些装饰器,因为它们是接收装饰函数并负责创建 的Cache
装饰器。(操场)