Python 装饰器的 3 种类型提示
Python 3 type hinting for decorator
考虑以下代码:
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
我想不出正确的输入方式。我知道在这个例子中,装饰函数和返回函数在技术上应该具有相同的签名(但即使这样也没有被检测到)。
你不能用Callable
来说明任何附加参数;它们不是通用的。您唯一的选择是说您的装饰器采用 Callable
并且不同的 Callable
是 returned.
在你的情况下,你 可以 用 typevar 确定 return 类型:
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
更加灵活的各种提议,但这些提议尚未实现。参见
- Allow variadic generics
- Proposal: Generalize
Callable
to be able to specify argument names and kinds
- TypeVar to represent a Callable's arguments
- Support function decorators excellently
举一些例子。该列表中的最后一个是包含您的特定用例的伞票,更改可调用签名的装饰器:
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 在接受答案后被接受,我们现在在 Python 3.10 中有 typing.ParamSpec
和 typing.Concatenate
。有了这些变量,我们就可以正确的打出一些操作位置参数的装饰器了。
请注意,mypy 对 PEP 612 的支持仍在进行中 (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
我在 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
使用decohints
库解决了问题:
pip install decohints
以下是它如何与您的代码一起工作:
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)
如果您在 PyCharm 中键入以下 foo()
并等待,它将显示 foo
函数参数提示 (a: int, username: str)
.
这里是 link 到 decohints
来源,还有其他解决此问题的选项:https://github.com/gri-gus/decohints
考虑以下代码:
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
我想不出正确的输入方式。我知道在这个例子中,装饰函数和返回函数在技术上应该具有相同的签名(但即使这样也没有被检测到)。
你不能用Callable
来说明任何附加参数;它们不是通用的。您唯一的选择是说您的装饰器采用 Callable
并且不同的 Callable
是 returned.
在你的情况下,你 可以 用 typevar 确定 return 类型:
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
更加灵活的各种提议,但这些提议尚未实现。参见
- Allow variadic generics
- Proposal: Generalize
Callable
to be able to specify argument names and kinds - TypeVar to represent a Callable's arguments
- Support function decorators excellently
举一些例子。该列表中的最后一个是包含您的特定用例的伞票,更改可调用签名的装饰器:
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 在接受答案后被接受,我们现在在 Python 3.10 中有 typing.ParamSpec
和 typing.Concatenate
。有了这些变量,我们就可以正确的打出一些操作位置参数的装饰器了。
请注意,mypy 对 PEP 612 的支持仍在进行中 (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
我在 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
使用decohints
库解决了问题:
pip install decohints
以下是它如何与您的代码一起工作:
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)
如果您在 PyCharm 中键入以下 foo()
并等待,它将显示 foo
函数参数提示 (a: int, username: str)
.
这里是 link 到 decohints
来源,还有其他解决此问题的选项:https://github.com/gri-gus/decohints