繁体   English   中英

如何以与原始函数可见的相同顺序处理包装函数参数?

[英]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.

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