装饰器的Python类型注释

时间:2020-04-22 11:07:50

标签: python

我有一个装饰器,它检查实例属性self.enabled并在未启用的情况下返回0。否则,它返回方法的返回值(即int),即传入列表中唯一字符串的索引。

def check_if_enabled(func: Callable[..., int]) -> Callable[..., int]:
    @wraps(func)
    def wrapper(cls, list_of_strings):
        if not cls.enabled:
            return 0
        return func(cls, list_of_strings)
    return wrapper

我想使类型注释更具体,但是我不确定如何做。

Callable[..., int]显然是我要更改的,我想这样做,所以Callable接受两个参数,一个类的实例和一个字符串列表。这可能吗?

1 个答案:

答案 0 :(得分:0)

首先,请记住尽可能提供完整的工作示例。用更多的代码和更少的文字记录您的问题,可以更轻松地理解您要实现的目标,并且对我们造成的误解也较小。

我假设您想装饰一种类似于以下内容的方法:

class Example(object):
    @check_if_enabled
    def func(self,  # implicit Example
             list_of_strings: List[str]
             ) -> int:
        pass

首先要做的是确定Callable的外观。您将函数参数列表作为第一个参数传递,并将返回值作为第二个参数传递。注意,第一个参数是self,即类实例。因此,可能是:

Callable[[Example, List[str]], int]

或者如果您不想将其限制为一个特定的类:

Callable[[object, List[str]], int]

请注意,您可能需要在声明Example之前使用此原型。为了允许这样做,您需要传递"Example",即类名作为字符串。这被视为该类的前向声明。


因此注释的代码为:

def check_if_enabled(func: Callable[["Example", List[str]], int]
                     ) -> Callable[["Example", List[str]], int]:
    @wraps(func)
    def wrapper(cls: "Example",
                list_of_strings: List[str]
                ) -> int:
        if not cls.enabled:
            return 0
        return func(cls, list_of_strings)
    return wrapper

或作为一个完整的工作示例:

from functools import wraps
from typing import Callable, List


def check_if_enabled(func: Callable[["Example", List[str]], int]
                     ) -> Callable[["Example", List[str]], int]:
    @wraps(func)
    def wrapper(cls: "Example",
                list_of_strings: List[str]
                ) -> int:
        if not cls.enabled:
            return 0
        return func(cls, list_of_strings)
    return wrapper


class Example(object):
    def __init__(self, enabled: bool) -> None:
        self.enabled = enabled

    @check_if_enabled
    def func(self,  # implicit Example
             list_of_strings: List[str]
             ) -> int:
        print('yes, it is')
        return 10


ex = Example(True)
print(ex.func(['1', '2', '3']))

ex = Example(False)
print(ex.func(['1', '2', '3']))

产生:

yes, it is
10
0

作为一般说明,您可能希望装饰器是通用的,而不是仅适合一种特定的方法。例如,可以将上述装饰器扩展为适合使用int和轻松键入的返回*args, **kwargs的任何方法:

def check_if_enabled(func: Callable[..., int]
                     ) -> Callable[..., int]:
    @wraps(func)
    def wrapper(cls: "Example",
                *args: Any,
                **kwargs: Any
                ) -> int:
        if not cls.enabled:
            return 0
        return func(cls, *args, **kwargs)
    return wrapper

更进一步,您可以允许通用返回值,并将其作为参数传递给装饰器:

def check_if_enabled(return_if_disabled: Any
                     ) -> Callable[[Callable[..., int]],
                                   Callable[..., int]]:
    def decorator(func: Callable[..., int]
                  ) -> Callable[..., int]:
        @wraps(func)
        def wrapper(cls: "Example",
                    *args: Any,
                    **kwargs: Any
                    ) -> Any:
            if not cls.enabled:
                return return_if_disabled
            return func(cls, *args, **kwargs)
        return wrapper
    return decorator


class Example(object):
    def __init__(self, enabled: bool) -> None:
        self.enabled = enabled

    @check_if_enabled(return_if_disabled=0)
    def func(self,  # implicit Example
             list_of_strings: List[str]
             ) -> int:
        print('yes, it is')
        return -1

当然,这会完全杀死静态类型。


如有疑问,建议您使用mypy。如果输入错误,它可以建议正确的类型。

最后,我不认为@wraps在这里没有做任何事情。我保留了它,因为它存在于粘贴的代码中。

希望这会有所帮助。

相关问题