代码之家  ›  专栏  ›  技术社区  ›  Thom Smith

向回调传递额外的可选参数,而不中断现有回调

  •  3
  • Thom Smith  · 技术社区  · 6 年前

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

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

    我知道这可以用 inspect . 我想知道是否有一个“惯用的”或常用的解决方案不是那么重。

    3 回复  |  直到 6 年前
        1
  •  4
  •   Christoforus Surjoputro    6 年前

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

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

    此代码未经测试,但应该可以工作。

        2
  •  1
  •   blhsing    6 年前

    一个简单的解决方案是使用 try 块以尝试先用第二个参数调用回调,然后返回到仅使用中的一个参数调用 except 块:

    try:
        callback(first, second)
    except TypeError as e:
        if e.__traceback__.tb_frame.f_code.co_name != 'func_name':
            raise
        callback(first)
    
        3
  •  -1
  •   Thom Smith    6 年前

    使用函数包装器:

    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])