[英]How to process wrapped function arguments in the same order as visible for original function?
我试图在调用之前检查函数参数的类型(在这个例子中是foo
)。 我正在使用 python 装饰器来实现这一点。 我看不到如何以与函数foo
可见的顺序相同的顺序获取参数。 在下面的示例中,我得到两个不同的排序,但具有基本相同的函数调用。
def wrapper(func):
def f(*args, **kwargs):
print([type(x) for x in args] + [type(v) for v in kwargs.values()])
return func(*args, **kwargs)
return f
@wrapper
def foo(a, b, c, d):
print(f"{a} {b} {c} {d}")
foo(10, 12.5, 14, 5.2) # all good: int, float, int, float
foo(10, 12.5, d=5.2, c=14) # not what I want: int, float, float, int
是否有可能以一致的顺序获得论点? 如果不是,那么至少有可能让它们全部由参数名称键入吗? 看起来像这样的东西:
def wrapper(func):
def f(**kwargs):
# kwargs = {'a': 10, 'b': 12.5, 'c': 14, 'd': 5.2}
print([type(v) for v in kwargs.values()])
return func(*args, **kwargs)
return f
foo(10, 12.5, 14, 5.2) # obviously doesn't work
类型检查有点弱,只要您注释代码,注释就可以工作,但是可以通过使用标准库中的inspect
来实现更健壮的方法:
它提供对框架的完全访问,...以及您可能需要的一切。 在这种情况下, inspect.signature
可用于获取原始函数的签名以获取参数的原始顺序。 然后只需重新组合参数并将最终组传递回原始函数。 更多细节在评论中。
from inspect import signature
def wrapper(func):
def f(*args, **kwargs):
# signature object
sign = signature(func)
# use order of the signature of the function as reference
order = order = dict.fromkeys(sign.parameters)
# update first key-values
order.update(**kwargs)
# update by filling with positionals
free_pars = (k for k, v in order.items() if v is None)
order.update(zip(free_pars, args))
return func(**order)
return f
@wrapper
def foo(a, b, c, d):
print(f"{a} {b} {c} {d}")
foo(10, 12.5, 14, 5.2)
#10 12.5 14 5.2
foo(10, 12.5, d=5.2, c=14)
#10 12.5 14 5.2
该代码与annotations
兼容:
@wrapper
def foo(a: int, b: float, c: int, d: float) -> None:
print(f"{a} {b} {c} {d}")
注释的方式,不需要导入:
它是上述代码的副本,但使用__annotations__
属性来获取签名。 请记住,注解可能有也可能没有输出注解
def wrapper(func):
def f(*args, **kwargs):
if not func.__annotations__:
raise Exception('No clue... inspect or annotate properly')
params = func.__annotations__
# set return flag
return_has_annotation = False
if 'return' in params:
return_has_annotation = True
# remove possible return value
return_ = params.pop('return', None)
order = dict.fromkeys(params)
order.update(**kwargs)
free_pars = (k for k, v in order.items() if v is None)
order.update(zip(free_pars, args))
# update with return annotation
if return_has_annotation:
func.__annotations__ = params | {'return': return_}
return func(**order)
return f
@wrapper
def foo(a: int, b: float, c: int, d: float) -> None:
print(f"{a} {b} {c} {d}")
首先要注意的是关键字参数的实现是因为顺序对它们无关紧要,并且旨在在调用时按名称将值映射到特定参数。 所以在kwargs
上执行任何特定的命令没有多大意义(或者至少会让任何试图使用你的装饰器的人感到困惑)。 因此,您可能需要检查指定了哪些kwargs
并删除相应的参数类型。
接下来,如果您希望能够检查参数类型,您将需要提供一种方法来告诉装饰器您期望的类型,方法是向它传递一个参数(您可以在此处查看更多信息)。 这样做的唯一方法是传递一个字典,将每个参数映射到预期的类型:
@wrapper({'a': int, 'b': int, c: float, d: int})
def f(a, b, c=6.0, d=5):
pass
def wrapper(types):
def inner(func):
def wrapped_func(*args, **kwargs):
# be careful here, this only works if kwargs is ordered,
# for python < 3.6 this portion will not work
expected_types = [v for k, v in types.items() if k not in kwargs]
actual_types = [type(arg) for arg in args]
# substitute these in case you are dead set on checking for key word arguments as well
# expected_types = types
# actual_types = [type(arg) for arg in args)] + [type(v) for v in kwargs.items]
if expected_types != actual_types:
raise TypeError(f"bad argument types:\n\tE: {expected_types}\n\tA: {actual_types}")
func(*args, **kwargs)
return wrapped_func
return inner
@wrapper({'a': int, 'b': float, 'c': int})
def f(a, b, c):
print('good')
f(10, 2.0, 10)
f(10, 2.0, c=10)
f(10, c=10, b=2.0)
f(10, 2.0, 10.0) # will raise exception
现在,在这一切之后,我想指出这个功能在 python 代码中可能在很大程度上是不需要和不必要的。 Python 被设计为动态类型,因此任何类似于 Python 中的强类型的东西都违背了规律,大多数人都不会期望。
接下来,从 python 3.5 开始,我们可以访问内置的typing
包。 这使您可以指定希望在函数调用中接收的类型:
def f(a: int, b: float, c: int) -> int:
return a + int(b) + c
现在这实际上不会为您做任何类型断言,但它会让您清楚地知道您期望什么类型,并且大多数(如果不是全部)IDE 会给您视觉警告,表明您将错误的类型传递给函数。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.