检查是否可以使用其他函数的参数

时间:2017-07-20 09:40:11

标签: python python-3.x

如何检查是否可以使用与另一个函数相同的参数调用函数?例如,可以使用提供给 b 的所有参数调用 a

def a(a, b, c=None):
    pass

def b(a, *args, d=4,**kwargs): 
    pass

我想要的原因是我有一个基本功能:

def f(a, b):
    print('f', a, b)

以及回调列表:

def g(b, a):
    print('g', a, b)

def h(*args, **kwargs):
    print('h', args, kwargs)    

funcs = [g, h]

和一个接受任何东西的包装函数:

def call(*args, **kwargs):
    f(*args, **kwargs)
    for func in funcs:
        func(*args, **kwargs)

现在我想检查所有回调是否会接受提供给call()的参数,假设它们对f()有效。 出于性能原因,我不想在每次调用call()时检查参数,而是在将每个回调添加到回调列表之前检查每个回调。 例如,这些调用是可以的:

call(1, 2)
call(a=1, b=3)

但是这个应该失败,因为g的参数顺序错误:

call(1, b=3)

2 个答案:

答案 0 :(得分:2)

这需要一些有趣的研究,但我认为我已经涵盖了角落案例。其中一些是为了在添加新语法时保持与python 2的兼容性。

最有问题的部分是,某些命名(关键字)参数可以作为位置参数传递,或者根据传入的顺序需要。

有关更多信息,请参阅评论。

下面的代码将确保可以使用函数a的任何可能的有效参数组合来调用函数b。 (并不意味着相反)。 取消注释/添加try除块以获取true / valse结果而不是AssertionError。

import inspect

def check_arg_spec(a,b):
    """

    attrs of FullArgSpec object:

        sp.args = pos or legacy keyword arguments, w/ keyword at back
        sp.varargs = *args
        sp.varkw = **kwargs
        sp.defaults = default values for legacy keyword arguments @ 
        sp.args
        sp.kwdonly = keyword arguments follow *args or *, must be passed in by name
        sp.kwdonlydefaults = {name: default_val, .... }
        sp.annotatons -> currently not in use, except as standard flag for outside applications

    Consume order:

    (1) Positional arguments
    (2) legacy keyword argument = default (can be filled by both keyword or positional parameter)
    [
        (3) *args
        [
          (4) keyword only arguments [=default]
        ]
    ]
    (5) **kwds

    """
    a_sp = inspect.getfullargspec(a) 
    b_sp = inspect.getfullargspec(b)
    kwdfb = b_sp.kwonlydefaults or {}
    kwdfa = a_sp.kwonlydefaults or {}
    kwddefb = b_sp.defaults or []
    kwddefa = a_sp.defaults or []

    # try:
    akwd = a_sp.kwonlyargs
    if len(kwddefa):
        akwd += a_sp.args[-len(kwddefa):]
    bkwd = b_sp.kwonlyargs
    if len(kwddefb):
        bkwd += b_sp.args[-len(kwddefb):]


    # all required arguments in b must have name match in a spec.
    for bkey in (set(b_sp.args) ^ set(bkwd)) & set(b_sp.args) :
        assert bkey in a_sp.args


    # all required positional in b can be met by a
    assert (len(a_sp.args)-len(kwddefb)) >= (len(b_sp.args)-len(kwddefb))

    # if a has  *args spec, so must b
    assert not ( a_sp.varargs and b_sp.varargs is None )


    # if a does not take *args, max number of pos args passed to a is len(a_sp.args). b must accept at least this many positional args unless it can consume *args
    if b_sp.varargs is None:
        # if neither a nor b accepts *args, check that total number of pos plus py2 style keyword arguments for sg of b is more than a can send its way. 
        assert len(a_sp.args) <= len(b_sp.args)


    #  Keyword only arguments of b -> they are required, must be present in a.
    akws = set(a_sp.kwonlyargs) | set(a_sp.args[-len(kwddefa):])

    for nmreq in (set(b_sp.kwonlyargs)^set(kwdfb)) & set(b_sp.kwonlyargs):
         assert nmreq in akws

     # if a and b both accept an arbitrary number of positional arguments or if b can but a cannot, no more checks neccessary here

    # if a accepts optional arbitrary, **kwds, then so must b
    assert not (a_sp.varkw and b_sp.varkw is None)

    if b_sp.varkw is None:
        # neither a nor b can consume arbitrary keyword arguments
        # then b must be able to consume all keywords that a can be called w/th.
        for akw in akwd:
            assert akw in bkwd

          # if b accepts **kwds, but not a, then there is no need to check further
          # if both accept **kwds, then also no need to check further

    #     return True 
    # 
    # except AssertionError:
    # 
    #       return False

答案 1 :(得分:1)

不确定您真正想要的是什么,我非常确定您的问题可以通过更好的方式解决,但无论如何:

from inspect import getargspec

def foo(a, b, c=None):
    pass

def bar(a, d=4, *args, **kwargs):
    pass

def same_args(func1, func2):
    return list(set(getargspec(func1)[0]).intersection(set(getargspec(func2)[0])))

print same_args(foo, bar)
# => ['a']

same_args只需检查来自func1func2的参数,并在func1func2中返回仅包含相同参数的新列表。