Python - 更改装饰器中的注释

问题描述 投票:0回答:1

假设我有一个带有类型注释的函数:

def f(n: float) -> int:
    if random.random() >= n:
        raise Exception
    return 1

我想装饰这个函数,装饰后的版本将扩展原始函数的返回值:

def decorator(f):
    def decorated(max_tries: int, delay: int, *a, **kwa):  # annotations?
        for i in range(1, max_tries+1):
            try:
                return True, f(*a, **kwa)
            except Exception:
                log('try %d of %d failed' % (i, max_tries))
                time.sleep(delay)

        return False, None

    return decorated

在此示例中,我想将原始返回类型

int
更改为
(bool, Union[int, None])

我试过这个:

def decorator(f):
    @functools.wraps(f)
    def decorated(max_tries: int, delay: int, *a, **kwa):
        for i in range(1, max_tries+1):
            try:
                return True, f(*a, **kwa)
            except Exception:
                log('failed try %d of %d' % (i, max_tries))
                time.sleep(delay)

        return False, None

    ret = f.__annotations__['return']
    decorated.__annotations__['return'] = (bool, Union([ret, None]))

    return decorated

我得到了

TypeError: Cannot instantiate <class 'typing.UnionMeta'>

有人知道该怎么做吗?


编辑:

>>> type(ret)
<class 'typing.UnionMeta'>
>>> isinstance(ret, Union)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Program Files\Python35-32\lib\typing.py", line 563, in __instancecheck__
    raise TypeError("Unions cannot be used with isinstance().")
TypeError: Unions cannot be used with isinstance().

打字模块中的类到底是什么(例如

List
Union
等)?它们是在定义函数时实例化的吗?为什么不能手动实例化它们?


编辑2:

显然“实例化”

Union
的方法是使用与类型注释中相同的表示法:
Union[int, None]

def decorator(f):
    @functools.wraps(f)
    def decorated(max_tries: int, delay: int, *a, **kwa):
        for i in range(1, max_tries+1):
            try:
                return True, f(*a, **kwa)
            except Exception:
                log('failed try %d of %d' % (i, max_tries))
                time.sleep(delay)

        return False, None

    ret = f.__annotations__['return']
    decorated.__annotations__['return'] = (bool, Union[ret, None])

    return decorated

现在的问题是将参数

max_tries: int
delay: int
添加到修饰函数的注释中,并使这些参数出现在
help(decorated_f)
中。 有什么帮助吗?

python annotations python-decorators
1个回答
0
投票

您可以通过检查函数和/或弄乱包装器的

__signature__
来做到这一点,请参阅inspect.signature()。如果你只想要返回参数:

def decorator(f):
    orig = inspect.signature(f)

    def decorated(max_tries: int, delay: int,
                  *a, **kwa) -> tuple[bool, orig.return_annotation | None]:
        for i in range(1, max_tries+1):
            try:
                return True, f(*a, **kwa)
            except Exception:
                log('try %d of %d failed' % (i, max_tries))
                time.sleep(delay)

        return False, None

    return decorated

@decorator
def f(n: float) -> int:
    if random.random() >= n:
        raise Exception
    return 1

这给出了一个半令人满意的答案:

help(f)

decorated(max_tries: int, delay: int, *a, **kwa) -> tuple[bool, int | None]

还有一些调整,例如使用

functools.update_wrapper
并将
*a, **kwa
替换为原始参数:

import inspect
import functools

def decorator(f):
    def decorated(max_tries: int, delay: int, *a, **kwa):
        for i in range(1, max_tries+1):
            try:
                return True, f(*a, **kwa)
            except Exception:
                log('try %d of %d failed' % (i, max_tries))
                time.sleep(delay)

        return False, None

    orig = inspect.signature(f)
    wrap = inspect.signature(decorated)

    functools.update_wrapper(decorated, f)
    decorated.__signature__ = orig.replace(
        parameters=list(wrap.parameters.values())[:-2] + list(orig.parameters.values()),
        return_annotation=tuple[bool, orig.return_annotation | None]
    )

    return decorated

@decorator
def f(n: float) -> int:
    """ Even has a docstring """
    if random.random() >= n:
        raise Exception
    return 1

我们从

help(f)
得到:

f(max_tries: int, delay: int, n: float) -> tuple[bool, int | None]
    Even has a docstring
© www.soinside.com 2019 - 2024. All rights reserved.