考虑以下代码:

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),我想在参数中注入(inject)用户名。因此,我修改了功能签名。
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

您可以使用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:
            # ...

即使这样,当您使用foo()时,得到的修饰的def (*Any, **Any) -> builtins.bool*函数也具有reveal_type()的键入签名。

目前正在讨论各种建议,以使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

  • 对于一些例子。该列表中的最后一个是总括票,其中包括您的特定用例,用于更改可调用签名的装饰器:

    关于python - 装饰器的Python 3类型提示,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/47060133/

    10-12 18:51