假设我有一个带有类型注释的函数:
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)
中。
有什么帮助吗?
您可以通过检查函数和/或弄乱包装器的
__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