[英]Python 3 type hinting for decorator
Consider the following code:考虑以下代码:
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
This piece of code is working as intended.这段代码按预期工作。 Now imagine I want to extend this, and instead of just executing func(*args, **kwargs)
I want to inject the username in the arguments. Therefore, I modify the function signature.现在想象一下我想扩展它,而不是仅仅执行func(*args, **kwargs)
我想在 arguments 中注入用户名。因此,我修改了 function 签名。
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
I can't figure out a correct way to type this.我想不出正确的输入方式。 I know that on this example, decorated function and returned function should technically have the same signature (but even that is not detected).我知道在这个例子中,装饰 function 并返回 function 在技术上应该具有相同的签名(但即使这样也没有被检测到)。
You can't use Callable
to say anything about additional arguments;你不能使用Callable
来说明额外的参数; they are not generic.它们不是通用的。 Your only option is to say that your decorator takes a Callable
and that a different Callable
is returned.您唯一的选择是说您的装饰器接受一个Callable
并返回一个不同的Callable
。
In your case you can nail down the return type with a typevar:在您的情况下,您可以使用 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:
# ...
Even then the resulting decorated foo()
function has a typing signature of def (*Any, **Any) -> builtins.bool*
when you use reveal_type()
.即便如此,当您使用reveal_type()
时,生成的装饰foo()
函数的类型签名为def (*Any, **Any) -> builtins.bool*
reveal_type()
def (*Any, **Any) -> builtins.bool*
。
Various proposals are currently being discussed to make Callable
more flexible but those have not yet come to fruition.目前正在讨论各种提案,以使Callable
更加灵活,但尚未取得成果。 See看
Callable
to be able to specify argument names and kinds建议:将Callable
泛化为能够指定参数名称和种类for some examples.对于一些例子。 The last one in that list is an umbrella ticket that includes your specific usecase, the decorator that alters the callable signature:该列表中的最后一个是包含您的特定用例的伞式票证,即更改可调用签名的装饰器:
Mess with the return type or with arguments弄乱返回类型或参数
For an arbitrary function you can't do this at all yet -- there isn't even a syntax.对于任意函数,您根本无法执行此操作 - 甚至没有语法。 Here's me making up some syntax for it.这是我为它编写的一些语法。
PEP 612 was accepted after the accepted answer, and we now have typing.ParamSpec
and typing.Concatenate
in Python 3.10. PEP 612在接受答案后被接受,我们现在在 Python 3.10 中拥有typing.ParamSpec
和typing.Concatenate
。 With these variables, we can correctly type some decorators that manipulate positional parameters.有了这些变量,我们就可以正确键入一些操纵位置参数的装饰器。
Note that mypy's support for PEP 612 is still under way ( tracking issue ).请注意, mypy 对 PEP 612 的支持仍在进行中(跟踪问题)。
The code in question can be typed like this (though not tested on mypy for the reason above)有问题的代码可以这样输入(尽管由于上述原因未在 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
I tested this in Pyright.我在 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: ignore
The problem is solved using the decohints
library:使用decohints
库解决了这个问题:
pip install decohints
Here is how it will work with your code:以下是它如何处理您的代码:
from decohints import decohints
def get_authenticated_user():
return "John"
@decohints
def inject_user():
def decorator(func):
def wrapper(*args, **kwargs):
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)
If you type below foo()
in PyCharm and wait, it will show foo
function parameter hints (a: int, username: str)
.如果您在 PyCharm 中的foo()
下方键入并等待,它将显示foo
function parameter hints (a: int, username: str)
。
Here is a link to the decohints
sources, there are also other options for solving this problem: https://github.com/gri-gus/decohints这是decohints
源的链接,还有其他解决此问题的选项: https://github.com/gri-gus/decohints
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.