简体   繁体   English

Python 装饰器的类型注释

[英]Python Type annotations for decorators

I have a decorator that checks an instance attribute self.enabled and returns 0 if it is not enabled.我有一个装饰器,它检查实例属性self.enabled并在未启用时返回 0。 Otherwise it returns the return value of the method which is an int, the index of the a unique string in the passed in list.否则,它返回方法的返回值,它是一个 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

I'd like to make the Type Annotation more specific but I am not sure how to.我想让类型注释更具体,但我不知道该怎么做。

Callable[..., int] is obviously what I want to change, and I want to make it so the Callable takes two arguments, an instance of a class and a list of strings. Callable[..., int]显然是我想要改变的,我想让它这样Callable需要两个 arguments、一个 class 的实例和一个字符串列表。 Is this possible?这可能吗?

For a start, please remember to provide a full working example whenever possible.首先,请记住尽可能提供完整的工作示例。 Documenting your problem with more code and less words makes it easier to understand what you're trying to achieve, and less likely for us to misunderstand.用更多的代码和更少的文字记录你的问题可以更容易地理解你想要实现的目标,并且我们不太可能误解。

I presume that you want to decorate a method alike the following:我假设你想装饰一个类似以下的方法:

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

The first to do is to establish how the Callable for it would look like.首先要做的是确定它的Callable的外观。 You pass the list of function parameters as the first argument, and the return value as the second.您将 function 参数列表作为第一个参数传递,返回值作为第二个参数传递。 Note that the first parameter is self , ie the class instance.请注意,第一个参数是self ,即 class 实例。 Therefore, it would either be:因此,它要么是:

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

or if you don't want to limit it to one specific class:或者如果您不想将其限制为一个特定的 class:

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

Note that you will probably want to use this prototype before declaring Example .请注意,您可能希望在声明Example之前使用此原型。 In order to permit that, you need to pass "Example" , ie the class name as string.为了允许这样做,您需要传递"Example" ,即 class 名称作为字符串。 This is treated as a forward declaration for the class.这被视为 class 的前向声明。


So your code annotated would be:所以你的代码注释将是:

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

or as a complete working example:或作为一个完整的工作示例:

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']))

which produces:产生:

yes, it is
10
0

As a general note, you probably want your decorators to be generic rather than fit for only one specific method.作为一般说明,您可能希望您的装饰器是通用的,而不是只适合一种特定的方法。 For example, the above decorator could be extended to fit any method returning an int by using *args, **kwargs and relaxed typing:例如,上面的装饰器可以通过使用*args, **kwargs和宽松的类型来扩展以适应任何返回int的方法:

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

Going even further, you could allow a generic return value and pass it as a parameter to the decorator:更进一步,您可以允许通用返回值并将其作为参数传递给装饰器:

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

Though of course this kills static typing entirely.虽然这当然会完全杀死 static 打字。


If in doubt, I'd suggest using mypy .如果有疑问,我建议使用mypy It can suggest the correct type if you get it wrong.如果您弄错了,它可以建议正确的类型。

Finally, I don't think @wraps does anything here.最后,我不认为@wraps在这里做任何事情。 I've preserved it because it was present in the pasted code.我保留了它,因为它存在于粘贴的代码中。

Hope this helps.希望这可以帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM