检查有效参数

7 投票
3 回答
3328 浏览
提问于 2025-04-16 10:49

我们先定义几个函数:

def x(a, b, c): pass
def y(a, b=1, c=2): pass
def z(a=1, b=2, c=3): pass

现在,假设我们有一个指向 xyz 的指针(叫 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

撰写回答