在 StackOverflow 看到这个问题:How to check arguments of the function?,正好我自己也遇到了这个问题。 于是顺手把这个问题和最佳回答翻译过来。

提问者Oleg Tarasenko;最佳答案作者Alex Martelli


Question

我有一个这样定义的函数:

def f1 (a, b, c = None, d = None):
    .....

我想检查参数 ab ,使其不等于某些特定值,该怎么做? 比如我想确保它们的值不是空字符串('' 或者 ' ')。

我有想过下面这种方法。

arguments = locals()
for item in arguments:
    check_attribute(item, arguments[item])

然后检查,确保参数不是 '' 或者 ' ' 。 但在此例中它会检查 None 值(译注:这一句我没看懂),而我想避免这个。


Answer

典型的方法:

import sys

...

def check_attribute(name, value):
    """如果值是空字符串或空格,向 stderr 输出警告。

       包括 None 在内的其他值都可以接受,不产生警告。
    """
    if isinstance(value, basestring) and (not value or value.isspace()):
        print>>sys.stderr, "Invalid value %r for argument %r" % (value, name)

或者你可以根据自己的需求,对严重的问题发出警告或者引发异常。

比起在你需要检查参数的函数中直接使用,这部分检查更应该剥离到单独的函数里。 (前者将会成为粘在你程序逻辑中的一团检查代码——还是把它们分开为好)

def check_arguments(d):
    for name, value in d.iteritems():
        check_attribute(name, value)

要进行检查的函数就可以改为:

def f1 (a, b, c=None, d=None):
    check_arguments(locals())
    ...

也可以把它写成一个装饰器来使用。

@checked_arguments
def f1 (a, b, c=None, d=None):
    ...

下面我们让这些检查用的代码更加“骨肉分离”(译注:想了好久才憋出这个词 ==|||去耦合(感谢@比尔盖子 的提醒)。 不过注意,除非你有很多函数需要进行这种检查,否则这完全是矫枉过正。

参数名的自省(由于 inspect 模块,这一点才变得可行)用在装饰器中要比直接用在目标函数中复杂得多。 这也是为什么在此例中我最喜欢的设计是避免使用装饰器(简洁是极好的 ;-))。

编辑——在提问者的要求下(虽然并没有说明原因),说明如何实现一个参数检查的装饰器。

(在 Python 2.6 及更早的版本中的)主要问题是要在封装中构造一种等效于 locals() 调用产生的映射关系。

但是——如果你在使用 Python 2.7inspect.getcallargs 就可以帮你实现。于是问题变得简单多了,这样一来装饰器似乎值得一用。(如果你在使用 2.6 或者更早的版本,我仍然推荐避免使用装饰器来实现,针对这种特殊的场景,其复杂性会大幅提高。)

以下就是在 Python 2.7 环境下需要的所有代码(前文中定义的 check_arguments 函数可以重用):

import functools
import inspect

def checked_arguments(f):
	@functools.wraps(f)
	def wrapper(*a, **k):
	  	d = inspect.getcallargs(f, *a, **k)
	  	check_arguments(d)
	  	return f(*a, **k)
	return wrapper

Python 2.7 之前版本中实现的难度完全来自于如何实现等效的 inspect.getcallargs ——所以,我希望,如果你需要使用这类装饰器的话,去官方网站下载 Python 2.7 安装吧。(这样,你将可以得到额外的便利,并且能有更长的技术支持周期,毕竟 2.7 是 Python 2.x 分支的最后一个版本了。)