如何检查是否可以使用与另一个函数相同的参数调用函数?例如,可以使用提供给 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)
答案 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
只需检查来自func1
和func2
的参数,并在func1
和func2
中返回仅包含相同参数的新列表。