Considere o seguinte código:
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)
Se eu perguntar ao pyright o tipo de Cache.decorate
antes do @classmethod
wrapper (inspecionando a palavra decorate
no código acima), ele retorna:
(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]))
Parece-me que ele entende que P
(os tipos de argumento) e R
(os tipos de retorno) estão analisados corretamente.
Entretanto, se eu pedir para ele fazer uma introspecção Cache.decorate
no contexto em que está sendo usado como decorador, ele retorna:
(method) def decorate(**params: Unknown) -> ((f: ((...) -> Coroutine[Any, Any, Unknown])) -> ((...) -> Coroutine[Any, Any, Unknown]))
...ou seja, a relação entre tipos de entrada e tipos de saída foi totalmente descartada!