下面是一个例子:
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
型
我想不出正确的输入方法,我知道在这个例子中,修饰函数和返回函数在技术上应该有相同的签名(但即使这样也没有被检测到)。
4条答案
按热度按时间fkaflof61#
PEP 612在接受的答案之后被接受,现在我们在Python 3.10中有了
typing.ParamSpec
和typing.Concatenate
。有了这些变量,我们可以正确地输入一些操纵位置参数的装饰器。代码可以这样输入:
字符串
arknldoa2#
你不能用
Callable
来描述额外的参数,它们不是泛型的,你只能说你的装饰器接受一个Callable
,然后返回一个不同的Callable
。在你的例子中,你可以用一个typevar来确定返回类型:
字符串
即使这样,当您使用
reveal_type()
时,最终的修饰foo()
函数也具有def (*Any, **Any) -> builtins.bool*
的类型签名。目前正在讨论各种提案,以使
Callable
更加灵活,但这些提案尚未实现。Callable
to be able to specify argument names and kinds的列表中的最后一个是一个包含特定用例的伞票,它是修改可调用签名的装饰器:
修改返回类型或参数
对于一个任意的函数,你还不能这样做--甚至没有语法,这是我为它编的一些语法。
apeeds0o3#
我在Pyright测试了这个。
字符串
bvpmtnay4#
使用
decohints
库可以解决这个问题:字符串
以下是它如何与您的代码一起工作:
型
如果在PyCharm中输入
foo()
下面的内容并等待,它将显示foo
函数参数提示(a: int, username: str)
。这里有一个
decohints
源代码的链接,还有其他解决这个问题的方法:https://github.com/gri-gus/decohints