开发者

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):
    ...
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜