考虑以下代码:
from typing import Callable, Any
TFunc = Callable[..., Any]
def get_authenticated_user(): return "John"
def require_auth() -> Callable[TFunc, TFunc]:
def decorator(func: TFunc) -> TFunc:
def wrapper(*args, **kwargs) -> Any:
user = get_authenticated_user()
if user is None:
raise Exception("Don't!")
return func(*args, **kwargs)
return wrapper
return decorator
@require_auth()
def foo(a: int) -> bool:
return bool(a % 2)
foo(2) # Type check OK
foo("no!") # Type check failing as intended这段代码可以正常工作。现在假设我想要扩展它,而不是仅仅执行func(*args, **kwargs),我想在参数中注入用户名。因此,我修改了函数签名。
from typing import Callable, Any
TFunc = Callable[..., Any]
def get_authenticated_user(): return "John"
def inject_user() -> Callable[TFunc, TFunc]:
def decorator(func: TFunc) -> TFunc:
def wrapper(*args, **kwargs) -> Any:
user = get_authenticated_user()
if user is None:
raise Exception("Don't!")
return func(*args, user, **kwargs) # <- call signature modified
return wrapper
return decorator
@inject_user()
def foo(a: int, username: str) -> bool:
print(username)
return bool(a % 2)
foo(2) # Type check OK
foo("no!") # Type check OK <---- UNEXPECTED我想不出一种正确的方法来输入这个。我知道在这个例子中,修饰函数和返回函数在技术上应该具有相同的签名(但即使这样也没有被检测到)。
发布于 2017-11-02 01:17:36
不能使用Callable来说明其他参数;它们不是泛型的。您唯一的选择是,您的装饰器接受一个Callable,并返回一个不同的Callable。
在您的示例中,您可以使用typevar确定返回类型:
RT = TypeVar('RT') # return type
def inject_user() -> Callable[[Callable[..., RT]], Callable[..., RT]]:
def decorator(func: Callable[..., RT]) -> Callable[..., RT]:
def wrapper(*args, **kwargs) -> RT:
# ...即使这样,当您使用reveal_type()时,得到的修饰后的foo()函数的类型签名也是def (*Any, **Any) -> builtins.bool*。
目前正在讨论各种提案,以使Callable更加灵活,但这些提案尚未实现。看见
Callable to be able to specify argument names and kinds 下面是一些例子。列表中的最后一个是包含您的特定用例的总票证,即更改可调用签名的装饰器:
与返回类型或参数有关
对于任意函数,您还不能这样做--甚至没有语法。下面是我为它设计的一些语法。
发布于 2021-07-08 00:47:19
在被接受的答案之后,PEP 612被接受了,现在我们在Python3.10中有了typing.ParamSpec和typing.Concatenate。有了这些变量,我们就可以正确地输入一些操纵位置参数的装饰符。
请注意,mypy对PEP612的支持仍在 (tracking issue)中。
有问题的代码可以像这样输入(尽管由于上面的原因没有在mypy上测试)
from typing import Callable, ParamSpec, Concatenate, TypeVar
Param = ParamSpec("Param")
RetType = TypeVar("RetType")
OriginalFunc = Callable[Param, RetType]
DecoratedFunc = Callable[Concatenate[Param, str], RetType]
def get_authenticated_user(): return "John"
def inject_user() -> Callable[[OriginalFunc], DecoratedFunc]:
def decorator(func: OriginalFunc) -> DecoratedFunc:
def wrapper(*args, **kwargs) -> RetType:
user = get_authenticated_user()
if user is None:
raise Exception("Don't!")
return func(*args, user, **kwargs) # <- call signature modified
return wrapper
return decorator
@inject_user()
def foo(a: int, username: str) -> bool:
print(username)
return bool(a % 2)
foo(2) # Type check OK
foo("no!") # Type check should fail发布于 2021-09-04 22:03:35
我在Pyright中对此进行了测试。
from typing import Any, Callable, Type, TypeVar
T = TypeVar('T')
def typing_decorator(rtype: Type[T]) -> Callable[..., Callable[..., T]]:
"""
Useful function to typing a previously decorated func.
```
@typing_decorator(rtype = int)
@my_decorator()
def my_func(a, b, *c, **d):
...
```
In Pyright the return typing of my_func will be int.
"""
def decorator(function: Any) -> Any:
def wrapper(*args: Any, **kwargs: Any) -> Any:
return function(*args, **kwargs)
return wrapper
return decorator # type: ignorehttps://stackoverflow.com/questions/47060133
复制相似问题