在不破坏现有回调的情况下将额外的可选参数传递给回调

Pass extra optional arguments to callbacks without breaking existing callbacks

我有一个接受回调的 API 方法。回调需要一个参数。

我希望此方法将第二个参数传递给接受它的回调。但是,我必须保持与只接受原始参数的回调的兼容性。 (事实上​​ ,我希望大多数用户不会关心额外的参数,因此强制他们明确忽略它会很烦人。)

我知道这可以使用 inspect 来完成。我想知道是否有 "idiomatic" 或不那么重量级的常用解决方案。

使用函数包装器:

from inspect import signature, Parameter

def ignore_extra_arguments(function):
    positional_count = 0
    var_positional = False
    keyword_names = set()
    var_keyword = False

    for p in signature(function).parameters.values():
        if p.kind == Parameter.POSITIONAL_ONLY:
            positional_count += 1
        elif p.kind == Parameter.POSITIONAL_OR_KEYWORD:
            positional_count += 1
            keyword_names.add(p.name)
        elif p.kind == Parameter.VAR_POSITIONAL:
            var_positional = True
        elif p.kind == Parameter.KEYWORD_ONLY:
            keyword_names.add(p.name)
        elif p.kind == Parameter.VAR_KEYWORD:
            var_keyword = True

    if var_positional:
        new_args = lambda args: args
    else:
        new_args = lambda args: args[:positional_count]

    if var_keyword:
        new_kwargs = lambda kwargs: kwargs
    else:
        new_kwargs = lambda kwargs: {
            name: value for name, value in kwargs.items()
            if name in keyword_names
        }

    def wrapped(*args, **kwargs):
        return function(
            *new_args(args),
            **new_kwargs(kwargs)
        )

    return wrapped

它有效,但有点蛮力。

一个更简单的版本,假设 function 没有关键字或可变参数:

from inspect import signature

def ignore_simple(function):
    count = len(signature(function).parameters)
    return lambda *args: function(*args[:count])

一个更简单的解决方案是使用 try 块尝试首先使用第二个参数调用回调,然后再回退到只使用 except 块中的一个参数调用:

try:
    callback(first, second)
except TypeError as e:
    if e.__traceback__.tb_frame.f_code.co_name != 'func_name':
        raise
    callback(first)

我认为您可以使用 __code__ 查看回调需要多少参数。

if callback.__code__.co_argcount == 2:
    callback(arg1, arg2)
else:
    callback(arg1)

此代码未经测试,但应该可以使用。