check for valid arguments
So let's define a few functions:
def x(a, b, c): pass
def y(a, b=1, c=2): pass
def z(a=1, b=2, c=3): pass
Now, what's the best way to, given a pointer to x
, y
, or 开发者_JS百科z
(p
), a tuple of args (a
) and a dictionary of kwargs (k
), check to see if
p(*a, **kw)
would produce any exception regarding having not enough arguments or incorrect arguments, etc—without actually calling p(*a, **kw)
and then catching the exceptions raised.
Example
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
You can use the inspect
module's getargspec
function to determine the functions of the arguments and their defaults, if any, as well as if the function accepts varargs or keywordargs. This should be enough information to determine if a given tuple will satisfy the argument requirements.
Here's a start (have to run, but I figured posting a start was better than nothing).
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.
The best way that I can think of to do this is to create a new function with the exact same signature and defaults and try to call that and catch the exception. The body of the new function's code should just be pass
so that there are no side effects. There's nothing really profound here, the code is just tedious. It's worth noting that this does tie you to CPython internals. It works on 2.6. You'd have to port it to other versions but that shouldn't be too hard.
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
Running this code yields:
$ python argspec.py
True
False
True
True
True
Check out this method signature type check decorator:
Python 3: http://code.activestate.com/recipes/572161/
Python 2: same site/.../recipes/426123/
It is easier to use in Python 3 because it uses annotations for type specifications, very intuitive:
@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):
...
etc.
The Python 2 version is not so pretty, but still usable:
@takes(int, str, dict, kw1 = bool)
@returns(list)
def foo(a1, a2, a3, **kwargs):
...
精彩评论