检查有效参数
我们先定义几个函数:
def x(a, b, c): pass
def y(a, b=1, c=2): pass
def z(a=1, b=2, c=3): pass
现在,假设我们有一个指向 x
、y
或 z
的指针(叫 p
),还有一组参数(叫 a
)和一个字典(叫 k
),我们想要检查一下,
p(*a, **kw)
在调用 p(*a, **kw)
之前,是否会因为参数不够或者参数不正确等原因而产生任何异常——而且我们不想真的去调用这个函数,也不想捕捉可能出现的异常。
示例
def valid(ptr, args, kwargs): ... #implementation here
valid(x, ("hello", "goodbye", "what?"), {}) # => True
valid(x, ("hello", "goodbye"), {}) # => False
valid(y, ("hello", "goodbye", "what?"), {}) # => True
valid(y, (), {"a": "hello", "b": "goodbye", "c": "what"}) #=> True
valid(y, ("hello", "goodbye"), {"c": "what?"}) #=> True
3 个回答
0
看看这个方法签名类型检查的装饰器:
Python 3: http://code.activestate.com/recipes/572161/
Python 2: 同样的网站/.../recipes/426123/
在Python 3中使用起来更简单,因为它使用了注解来指定类型,这样非常直观:
@typecheck
def foo(a1: int, a2: str, a3: dict, *, kw1: bool) -> list:
...
@typecheck
def bar(el: list_of(str),
stream: with_attr("write", "flush"),
num: by_regex("^[0-9]+$"),
f: optional(callable) = None):
...
@typecheck
def biz(p: lambda x: isinstance(x, int) and x % 3 == 0):
...
等等。
Python 2的版本看起来没那么好,但还是可以用的:
@takes(int, str, dict, kw1 = bool)
@returns(list)
def foo(a1, a2, a3, **kwargs):
...
5
你可以使用 inspect
模块里的 getargspec
函数来查看一个函数的参数以及它们的默认值(如果有的话),还可以知道这个函数是否接受可变数量的参数或者关键字参数。这些信息应该足够你判断一个给定的元组是否符合函数的参数要求。
这是一个开始(我得走了,但我觉得发个开始总比什么都不发好)。
def valid(f, *args, **kwargs):
specs = getargspec(f)
required = specs.args[:-len(specs.defaults)] if (specs.defaults != None) else specs.args
#Now just check that your required arguments list is fulfilled by args and kwargs
#And check that no args are left over, unless the argspec has varargs or kwargs defined.
2
我想到的最好办法就是创建一个新的函数,和原来的函数完全一样,包括参数和默认值,然后尝试调用这个新函数,并捕捉可能出现的错误。这个新函数的代码部分可以简单写成 pass
,这样就不会有任何副作用。其实这里没有什么深奥的内容,只是代码写起来比较繁琐。值得注意的是,这种方法是依赖于CPython的内部实现的。在2.6版本上是可以工作的。如果要在其他版本上使用,你需要进行一些调整,但这应该不会太难。
import types
ARGS_FLAG = 4 #If memory serves, this can be found in code.h in the Python source.
KWARGS_FLAG = 8
def valid(f, args, kwargs):
def dummy():
pass
dummy_code = dummy.func_code
real_code = f.func_code
args_flag = real_code.co_flags & ARGS_FLAG
kwargs_flag = real_code.co_flags & KWARGS_FLAG
# help(types.CodeType) for details
test_code = types.CodeType(real_code.co_argcount,
real_code.co_nlocals,
dummy_code.co_stacksize,
args_flag | kwargs_flag,
dummy_code.co_code,
dummy_code.co_consts,
dummy_code.co_names,
real_code.co_varnames,
"<test>", "", 0, "", ())
# help(types.FunctionType) for details
test_func = types.FunctionType(test_code, {}, "test", f.func_defaults)
try:
test_func(*args, **kwargs)
except TypeError:
return False
else:
return True
def x(a, b, c): pass
def y(a, b=1, c=2): pass
def z(a=1, b=2, c=3): pass
print valid(x, ("hello", "goodbye", "what?"), {}) # => True
print valid(x, ("hello", "goodbye"), {}) # => False
print valid(y, ("hello", "goodbye", "what?"), {}) # => True
print valid(y, (), {"a": "hello", "b": "goodbye", "c": "what"}) #=> True
print valid(y, ("hello", "goodbye"), {"c": "what?"}) #=> True
运行这段代码会得到:
$ python argspec.py
True
False
True
True
True