考虑以下代码:
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
before @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]))
...也就是说,输入类型和输出类型之间的关系已经完全被丢弃了!
我可以通过将
decorate
设为独立函数而不是 Cache
类的一部分来避免此问题,如下所示:
class Cache[**P, R]:
...
def decorate[**P, R](**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[**P,R] here
return decorator
这个解决方法可以避免吗?为什么有必要?
decorator
取决于两个上下文类型变量:P
和 R
。在 Cache
类的上下文中,这些是未知的,但假定 Pyright 在调用时已知。
但是,当调用
Cache.decorate()
时,Pyright 没有获得足够的信息来解析 P
和 R
(没有显式类型参数,也没有参数),因此这两个被解析为 Unknown
。
一个简单的修复方法是显式参数化
Cache
:
@Cache[[int], int].decorate()
async def some_function(i: int) -> int:
return i + 1
reveal_type(some_function) # (int) -> Coroutine[Any, Any, int]
但是,这并没有让您找到问题的根源,即您没有正确指定您想要的内容。
Cache
是 P
和 R
的泛型,因此必须存在一种方式让 Pyright 可以推断出与这些参数相对应的类型。 decorate()
是一个工厂方法,但它本身不会创建 Cache
的实例。它需要一些参数,但这些不会影响装饰的类型some_function()
。
相反,
decorate()
旨在创建装饰器,这些装饰器本身创建Cache
的实例。 您想要参数化的是这些装饰器,因为它们是接收装饰函数并负责创建Cache
的人。
(游乐场)
class Cache[**P, R]:
@classmethod
def decorate(cls, **params: Any):
def decorator[**P2, R2](f: Callable[P2, Coroutine[Any, Any, R2]]) -> ...:
# ^^^^^^^^^^
return f
return decorator
reveal_type(Cache.decorate())
# (f: (**P2@decorator) -> Coroutine[Any, Any, R2@decorator]) -> ((**P2@decorator) -> Coroutine[Any, Any, R2@decorator])
reveal_type(some_function)
# (i: int) -> Coroutine[Any, Any, int]