我想验证(在运行时,这不是一个类型问题),作为参数传递的函数只需要1个位置变量(基本上,该函数将以字符串作为输入调用并返回一个truthy)。
天真地,这就是我所拥有的:
def check(v_in : Callable):
"""check that the function can be called with 1 positional parameter supplied"""
sig = signature(v_in)
len_param = len(sig.parameters)
if not len_param == 1:
raise ValueError(
f"expecting 1 parameter, of type `str` for {v_in}. got {len_param}"
)
return v_in
如果我检查下面的函数,它是OK的,这是好的:
def f_ok1_1param(v : str):
pass
但下一个失败,尽管*args
将接收1个参数,**kwargs
将为空
def f_ok4_vargs_kwargs(*args,**kwargs):
pass
from rich import inspect as rinspect
try:
check(f_ok1_1param)
print("\n\npasses:")
rinspect(f_ok1_1param, title=False,docs=False)
except (Exception,) as e:
print("\n\nfails:")
rinspect(f_ok1_1param, title=False,docs=False)
try:
check(f_ok4_vargs_kwargs)
print("\n\npasses:")
rinspect(f_ok4_vargs_kwargs, title=False,docs=False)
except (Exception,) as e:
print("\n\nfails:")
rinspect(f_ok4_vargs_kwargs, title=False,docs=False)
通过第一个,第二个失败,而不是两个都通过:
passes:
╭─────────── <function f_ok1_1param at 0x1013c0f40> ───────────╮
│ def f_ok1_1param(v: str): │
│ │
│ 37 attribute(s) not shown. Run inspect(inspect) for options. │
╰──────────────────────────────────────────────────────────────╯
fails:
╭──────── <function f_ok4_vargs_kwargs at 0x101512200> ────────╮
│ def f_ok4_vargs_kwargs(*args, **kwargs): │
│ │
│ 37 attribute(s) not shown. Run inspect(inspect) for options. │
╰──────────────────────────────────────────────────────────────╯
所有不同的签名组合定义如下:
def f_ok1_1param(v : str):
pass
def f_ok2_1param(v):
pass
def f_ok3_vargs(*v):
pass
def f_ok4_p_vargs(p, *v):
pass
def f_ok4_vargs_kwargs(*args,**kwargs):
pass
def f_ok5_p_varg_kwarg(param,*args,**kwargs):
pass
def f_bad1_2params(p1, p2):
pass
def f_bad2_kwargs(**kwargs):
pass
def f_bad3_noparam():
pass
现在,我已经检查了一些参数:
rinspect(signature(f_ok4_vargs_kwargs).parameters["args"])
╭─────────────────── <class 'inspect.Parameter'> ───────────────────╮
│ Represents a parameter in a function signature. │
│ │
│ ╭───────────────────────────────────────────────────────────────╮ │
│ │ <Parameter "*args"> │ │
│ ╰───────────────────────────────────────────────────────────────╯ │
│ │
│ KEYWORD_ONLY = <_ParameterKind.KEYWORD_ONLY: 3> │
│ kind = <_ParameterKind.VAR_POSITIONAL: 2> │
│ name = 'args' │
│ POSITIONAL_ONLY = <_ParameterKind.POSITIONAL_ONLY: 0> │
│ POSITIONAL_OR_KEYWORD = <_ParameterKind.POSITIONAL_OR_KEYWORD: 1> │
│ VAR_KEYWORD = <_ParameterKind.VAR_KEYWORD: 4> │
│ VAR_POSITIONAL = <_ParameterKind.VAR_POSITIONAL: 2> │
╰───────────────────────────────────────────────────────────────────╯
我想检查Parameter.kind
与_ParameterKind
的每个参数的枚举是需要如何处理的,但我想知道是否我想得太多了,或者是否已经存在这样做,无论是在inspect
中还是typing
protocol
支持是否可以这样做,* 但在运行时 *。
注意,理论上def f_ok_cuz_default(p, p2 = None):
也可以,但我们暂时忽略它。
p.s.动机是在验证框架中提供自定义回调函数。调用位置位于框架的深处,并且该特定参数也可以是字符串(转换为正则表达式)。它甚至可以是没有。这里最简单的是贴一个def myfilter(*args,**kwargs): breakpoint
。或者myfilter(foo)
。然后看看你从框架中得到了什么,调整身体。函数中有异常是一回事,框架接受它,但在调用它之前出错是另一回事。所以我问你“我们叫它的时候它能用吗?”更方便用户使用。
2条答案
按热度按时间ruyhziif1#
我不认为你的问题是微不足道的。我不知道任何给定的实现,所以我跟随你的思路并得出结论,最终,问题归结为回答以下问题:
1.可调用对象有任何参数吗?
1.如果是,那么第一个参数是 * 位置 * 吗?
1.如果是,所有其他参数都是 * 可选 * 的吗?
如果你对所有问题的回答都是yes,那么你的函数可以用一个(位置)参数调用,否则不能。在这方面,
f(a)
,或f(a, /)
,或f(*args)
;f(a, *args)
,或f(a, **kwargs)
,或f(a, b=None)
。实现
您可以执行相应的检查,如下所示:
测试用例
这将返回测试用例的正确结果(我扩展了一点):
最后两个想法:
1.首先,我认为第二个问题需要更一般地表述,即:any 参数是位置性的吗?然而,我没有提出任何组合,其中第一个参数不一定是位置参数(根据给定的 positional 定义),并且我很有信心这在当前的Python语法规则中是不可能的。
1.这个解决方案肯定不是最有效的,考虑到你已经检查过的参数的知识,某些其他的参数是不可能遵循的,因此实际上不需要再次检查(例如:例如,如果第一个可选参数是
**kwargs
,则不能再跟随可选的*args
)。更新以澄清:所提供的解决方案将在某种意义上“工作”,它检查给定可调用对象的 * 签名 * 是否与仅用一个位置参数调用它兼容。它 * 不能 * 确保的是一个给定的位置参数满足可调用的 * 内部逻辑 *。例如,如果
f(*args)
在内部尝试访问args
中第一个元素之后的元素,如果只使用一个参数调用,即使建议的检查允许它通过,它也会失败。除了运行可调用对象或检查其实际代码之外,这种方式从外部检查内部逻辑的工作并不多。(此外,据我理解,问题并没有问及后者。)xmjla07d2#
我也写了我自己的函数后,张贴这一点。我已经从接受的答案中复制了默认处理(并且从another answer中复制了更干净的
.kind
检查),但我的方法不同,我一旦知道后面没有 * 必需的位置 *,就立即退出检查循环。令人惊讶的复杂 * 和 * 难以概括。检查另一个签名(比如这次是2个位置)的东西将不得不重做大部分。