装饰器的Python 3类型提示 [英] Python 3 type hinting for decorator

查看:147
本文介绍了装饰器的Python 3类型提示的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

请考虑以下代码:

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),我想将用户名注入参数中。因此,我修改了函数签名。

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.

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).

推荐答案

您不能使用 Callable 谈论其他参数。它们不是通用的。唯一的选择是说您的装饰器采用 Callable ,并返回不同的 Callable

You can't use Callable to say anything about additional arguments; they are not generic. Your only option is to say that your decorator takes a Callable and that a different Callable is returned.

在您的情况下,您可以可以使用类型变量确定返回类型:

In your case you can nail down the return type with a 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)->当您使用 reveal_type()时,builtins.bool *

Even then the resulting decorated foo() function has a typing signature of def (*Any, **Any) -> builtins.bool* when you use reveal_type().

当前有各种建议正在讨论使 Callable 更加灵活,但这些措施尚未实现。参见

Various proposals are currently being discussed to make Callable more flexible but those have not yet come to fruition. See

  • 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

例如。该列表中的最后一个是一张总括票,其中包括您的特定用例,修饰器,该修饰器更改了可调用的签名:

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 return类型或带有参数



对于任意函数,您根本无法做到这一点-甚至没有语法。这是我为它编写的一些语法。

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.

这篇关于装饰器的Python 3类型提示的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆