如何编写一个通用的Python 2.2函数返回未设置的参数列表?

1 投票
7 回答
1283 浏览
提问于 2025-04-17 07:34

我有一个函数,它需要很多输入参数。我想要一个函数,能够返回一个参数名的列表,这些参数的值要么是空字符串,要么是None。

通常在这种情况下,我会抛出一个异常。如果有人想通过抛出异常来解决这个问题,那也没问题。不过我还是希望这个函数能返回参数名的列表。

总结一下:

  1. 返回一个未设置参数的名称列表。
  2. “未设置”意味着参数的值不是空字符串或None。
  3. 接受一个参数:一个一维列表或字典。
  4. 这个列表应该包含所有空参数的名称。
  5. 我需要它与Python 2.2和Jython兼容。
  6. 2.2是必须的。代码必须能在我们无法升级的旧系统上运行。真是让人头疼。
  7. 这些参数不是命令行参数,而是函数的参数。
  8. 这些参数存储在单独的变量中,但如果需要,我可以手动把它们放进字典里。
  9. 与其返回Python变量名的列表,不如返回每个空变量的用户友好描述。例如:“数据库名称”而不是“db_name”。

对提出的问题的回答:

  1. 如果遇到未知参数怎么办?我们不在乎。我们创建参数列表来验证,只选择那些根据系统逻辑是必需的参数。因此,我们不会把未知参数放入要验证的列表中。
  2. 对于那些不是必需的UI参数,或者必须以其他方式验证的参数(比如整数与字符串等),我们不会把非必需参数放入传递给验证函数的列表中。对于其他更复杂的验证,我们会单独处理。这个函数之所以看起来方便,是因为空参数是我们最常见的验证,而在多个函数中为每个参数写一个if not foo:会变得很繁琐。
  3. 请解释一下“由于我们平台的特性”。还有“它以单独变量的形式到达”...这些单独变量在什么命名空间?“(预处理)”是什么意思? – 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 个回答

0

这是一个常见的“变量很多”的写法。

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,这要看情况)。

当出现问题时,你可以打印出对用户友好的函数描述。

这样做效果很好,几乎不需要太多编程。

1

为什么不把它做成一个装饰器呢?

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, "")

当然,你可以调整这个装饰器,让它为你处理这些事情,而不是在每个函数中都写重复的代码。

编辑:修复了一些小错误

2

我不太明白这个问题的意思,也不太清楚你所说的“最佳解决方案”是怎么满足要求的,不过根据你提到的内容:

我有一个函数,它有很多输入参数,我需要一个函数,能够返回那些值为''或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()只获取参数,而不是其他局部变量。

撰写回答