如何编写一个通用的Python 2.2函数返回未设置的参数列表?
我有一个函数,它需要很多输入参数。我想要一个函数,能够返回一个参数名的列表,这些参数的值要么是空字符串,要么是None。
通常在这种情况下,我会抛出一个异常。如果有人想通过抛出异常来解决这个问题,那也没问题。不过我还是希望这个函数能返回参数名的列表。
总结一下:
- 返回一个未设置参数的名称列表。
- “未设置”意味着参数的值不是空字符串或None。
- 接受一个参数:一个一维列表或字典。
- 这个列表应该包含所有空参数的名称。
- 我需要它与Python 2.2和Jython兼容。
- 2.2是必须的。代码必须能在我们无法升级的旧系统上运行。真是让人头疼。
- 这些参数不是命令行参数,而是函数的参数。
- 这些参数存储在单独的变量中,但如果需要,我可以手动把它们放进字典里。
- 与其返回Python变量名的列表,不如返回每个空变量的用户友好描述。例如:“数据库名称”而不是“db_name”。
对提出的问题的回答:
- 如果遇到未知参数怎么办?我们不在乎。我们创建参数列表来验证,只选择那些根据系统逻辑是必需的参数。因此,我们不会把未知参数放入要验证的列表中。
- 对于那些不是必需的UI参数,或者必须以其他方式验证的参数(比如整数与字符串等),我们不会把非必需参数放入传递给验证函数的列表中。对于其他更复杂的验证,我们会单独处理。这个函数之所以看起来方便,是因为空参数是我们最常见的验证,而在多个函数中为每个参数写一个
if not foo:
会变得很繁琐。 请解释一下“由于我们平台的特性”。还有“它以单独变量的形式到达”...这些单独变量在什么命名空间?“(预处理)”是什么意思? – John Machin 2天前。回答:这些变量在全局命名空间中。我们使用代码注入(类似于C预处理器如何用宏名称替换代码,只不过我们是用变量值替换标签,类似这样:
DATABASE_NAME = ^-^在这里放用户输入的数据库名称^-^
在预处理器运行后,结果如下:
DATABASE_NAME = "DB1"
这里有一个具体的例子,说明为什么简单的方法抛出异常不适用。我已经根据要求重写了代码,使用异常而不是返回值:
def validate_parameters(params_map):
"""
map is like {foo: "this is foo"}
"""
missing_params_info = []
for k,v in params_map.items():
if not k:
missing_params_info.append(v)
if missing_params_info:
raise TypeError('These parameters were unset: %s' % missing_params_info)
params = {}
params['foo'] = '1'
params['bar'] = '2'
params['empty'] = ''
params['empty2'] = ''
params['None'] = None
params_map = {
params['foo']: 'this is foo',
params['bar']: 'this is bar',
params['empty']: 'this is empty',
params['empty2']: 'this is empty2',
params['None']: 'this is None',
}
print validate_parameters(params_map)
bash-3.00# python /var/tmp/ck.py
Traceback (most recent call last):
File "/var/tmp/ck.py", line 26, in ?
print validate_parameters(params_map)
File "/var/tmp/ck.py", line 10, in validate_parameters
raise TypeError('These parameters were unset: %s' % missing_params_info)
TypeError: These parameters were unset: ['this is empty2', 'this is None']
它对我们不适用的两个原因:它只打印了empty2,尽管还有另一个空参数“empty”。“empty”被“empty2”覆盖,因为它们在映射中使用了相同的键。
第二个原因:我需要在运行这个函数后,将描述列表放入一个变量中。也许用异常可以做到这一点,但我现在不知道怎么做。
我已经发布了一个看起来能解决所有这些问题的答案,但并不理想。我标记了问题为已回答,但如果有人发布了更好的答案,我会更改这个标记。
谢谢!
7 个回答
这是一个常见的“变量很多”的写法。
def function( variable1, variable2, variable3, ..., variablen ):
"""user-friendly description of the function.
:param variable1: meaning, units, range, whatever.
:param variable2: meaning, units, range, whatever.
...
:param variablen: meaning, units, range, whatever.
:returns: range, type, whatever.
"""
# do the processing
不要检查缺失或无效的参数。Python已经帮你做了所有需要的类型检查。只需写你的代码,不需要特别去“验证”输入。
当出现异常时,说明输入有问题。
就是这么简单。
不要通过写很多额外的if语句来让事情变得复杂,重新实现Python的类型检查。
还有。
绝对不要把“错误返回”和有效返回混在一起。任何类型的错误输入都必须导致异常。好的输入返回好的值,坏的输入则会抛出异常。
就是这么简单。不要让事情变得复杂。
调用这个函数时,你可以这样做:
the_variables = {
'variable1': some value,
'variable2': some value,
...
'variablen': some value,
}
try:
function( **the_variables )
except Exception:
print( function.__doc__ )
如果缺少什么,你会得到一个TypeError
。如果有东西是错误的None
或者空的,你会得到一个ValueError(或者TypeError,这要看情况)。
当出现问题时,你可以打印出对用户友好的函数描述。
这样做效果很好,几乎不需要太多编程。
为什么不把它做成一个装饰器呢?
def argsnotempty(**requiredargs):
def decorator(func):
def wrapper(*args, **kwargs):
code = func.func_code
argsreq = code.co_argcount - 1
argsrec = len(args)
posargs = code.co_varnames[1:argsreq + 1]
errs = []
# validate positional args
for i, arg in enumerate(args):
if i == len(posargs):
break
# falsy but not False: 0, '', None, [], etc.
if not (arg or arg is False):
argname = posargs[i]
if argname in requiredargs:
errs.append(argname + " (" + requiredargs[argname] + ")")
# validate keyword args
for argname, arg in kwargs.iteritems():
if argname in requiredargs:
if not (arg or arg is False):
errs.append(argname + " (" + requiredargs[argname] + ")")
# make sure all required args are present
for argname in requiredargs:
if argname not in kwargs and argname not in posargs:
errs.append(argname + " (" + requiredargs[argname] + ")")
return func(errs, *args, **kwargs)
wrapper.__name__, wrapper.__doc__ = func.__name__, func.__doc__
return wrapper
return decorator
这个装饰器会检查你传入的参数是否为空,然后用一组“友好的”参数名称(那些是空的)作为第一个参数来调用被包装的函数。它还会尝试检查关键字参数。没有在装饰器中指定的参数是不会被检查的。
用法:
@argsnotempty(a="alpha", b="beta", g="gamma")
def foo(errs, a, b, g):
print errs
foo(3.14, "blarney", None) # prints "['g (gamma)']"
这里有一个例子,如果你没有得到需要的值,就会抛出一个异常:
@argsnotempty(a="alpha", b="beta", g="gamma")
def bar(errs, a, b, g):
if errs:
raise ValueError("arguments " + ", ".join(errs) + " cannot be empty")
bar(0, None, "")
当然,你可以调整这个装饰器,让它为你处理这些事情,而不是在每个函数中都写重复的代码。
编辑:修复了一些小错误
我不太明白这个问题的意思,也不太清楚你所说的“最佳解决方案”是怎么满足要求的,不过根据你提到的内容:
我有一个函数,它有很多输入参数,我需要一个函数,能够返回那些值为''或None的参数名称(而不是它们的值)。
这里有个简单的方法可以实现你所提到的功能:
def validate_parameters(args):
unset = []
for k in args:
if args[k] is None or args[k]=="":
unset.append(k)
return unset
然后只需在函数的第一行调用validate_parameters:
def foo(a, b, c):
print "Unset:", validate_parameters(locals())
>>> foo(1, None, 3)
Unset: ['b']
>>> foo(1, None, "")
Unset: ['c', 'b']
如果不是因为需要支持Python 2.2,你可以用一行代码来完成所有的事情。关键是你必须在函数的第一行调用它,这样才能确保locals()
只获取参数,而不是其他局部变量。