检查是否仅设置了一个可选功能参数并获取其值

时间:2018-06-19 14:46:51

标签: python python-3.x function

我具有以下功能

def foo(a, b=None, c=None, d=None)

其中可选参数bcd中,只有一个应设置为数值(不允许为零)。该函数还应该接受其他可选参数,为清楚起见,我在定义中将其省略。

要对此进行检查,我会得到如下参数值和名称的列表:

def foo(a, b=None, c=None, d=None):
    print(locals().keys())
    print(locals().values())

例如:

foo(a=1, b=2)
-
dict_keys(['d', 'c', 'b', 'a'])
dict_values([None, None, 2, 1])

符合预期。然后,下面的列表理解应根据其值检查可选参数,并仅返回具有真实值的参数。

fov_arg = [(arg, list(locals().values())[i]) for i, arg in enumerate(locals().keys() \
           if arg in ['b', 'c', 'd'] and list(locals().values())[i]]

然后我可以assert len(fov_arg) == 1看到仅设置了一个可选参数。我使用python3.6时,locals().values()被包装到list()调用中,其中locals().values()返回一个dict视图,而不是列表。

但是,使用列表理解来运行函数

def foo(a, b=None, c=None, d=None):
    print(locals().keys())
    print(locals().values())

    fov_arg = [(arg, list(locals().values())[i]) for i, arg in enumerate(locals().keys() \
           if arg in ['b', 'c', 'd'] and list(locals().values())[i]]

foo(a=1, b=2)

输出以下内容:

dict_keys(['d', 'c', 'b', 'a'])
dict_values([None, None, 2, 1])

[('d', 'd'), ('c', 1), ('b', <enumerate object at 0x7fd90c43ba20>)]

我希望输出为[(b, 2)]。我不明白为什么dc在此列表中,因为它们的值是None。另外,它们两个都显示错误的值,并且b的值是enumerate object

要弄清楚出了什么问题,我将列表理解重写为

for i, arg in enumerate(locals().keys()):
        if arg in ['b', 'c', 'd']:
            if list(locals().values())[i]:
                print(arg, list(locals().values())[i])

使用与上面相同的参数调用函数会给我

File "test.py", line 3, in foo
    for i, arg in enumerate(locals().keys()):
RuntimeError: dictionary changed size during iteration

我发现字典locals().keys()似乎与问题有关,但我不明白出了什么问题以及如何解决此问题。

4 个答案:

答案 0 :(得分:1)

  

出了什么问题

每次创建局部变量时,locals()词典都会更新。特别是,在调用enumerate(locals().keys())之后和调用locals().values())[i]之前,您将创建局部变量iarg。因此,前者返回的决定与后者返回的决定具有不同的成员资格。

类似地,在您的for循环中:

for i, arg in enumerate(locals().keys()):

在第一次迭代期间,locals()仅具有abcd。在第二次迭代中,它还具有iarg

  

以及如何解决此问题。

选择一种更简单的方式来解释您的论点。我可以建议:

sum(i is not None for i in [b, c, d]) == 1

注释中列出了其他可能更好的解决方案。

答案 1 :(得分:1)

如果没有选择**kwargs,请遵循@Rob的回答,也许可以进行以下修改:

def foo(a, b=None, c=None, d=None):
    localvars = locals()
    fov_arg = [(arg,localvars[arg]) for arg in localvars.keys()\
    if arg in ['b', 'c', 'd'] and localvars[arg]]

我已经简化了您的理解,我认为enumerate尤其是可以避免的。

答案 2 :(得分:0)

我建议通过一个简单的电话进行所有检查:

def foo(a, **kwargs):
    [(name, value)] = list(kwargs.items())
    assert name in 'bcd'
    # now process a, name, and value

现在,您必须将a以及恰好一个附加参数传递给该函数,否则赋值将失败。附加参数的名称必须为bcd中的一个,否则assert将失败。

这样可以减少混乱,并且在name变量中有了附加参数的名称。

如果您有其他可选参数,则可以轻松地与此配合使用:

def foo(a, some_other_optional_parameter=some_default_value, **kwargs):
    [(name, value)] = list(kwargs.items())
    assert name in 'bcd'
    # now process a, name, value, and some_other_optional_parameter

这可以称为

foo(4, c=5)

或为

foo(4, c=5, some_other_optional_parameter=6)

答案 3 :(得分:0)

您可以使用特定的签名来声明参数,然后定义函数以接受*kwargs。不错的一点是,任何智能编辑器都可以使用签名来建议正确的参数,并且inspect模块将能够自动检查其正确性,从而更易于维护。

缺点是添加其他关键字参数时需要自定义

def foo(a, *, b=None, c=None, d=None):   # force b, c, and d to be passed by keyword
    pass

sig = inspect.signature(foo)

def foo(a, *args, **kwargs):
    if len(kwargs) != 1 or len(args) != 0 
        or (tuple(kwargs.values()))[0] is None):
        raise ValueError("1 and only 1 of b,c, d parameters required")
    bound = sig.bind_partial(a, *args, **kwargs)
    bound.apply_defaults()

    # actual processing...
    def do_process(a, b, c, d):
        print("foo", a, b, c, d)
    do_process(**bound.arguments)

foo.__signature__ = sig

效果很好:

>>> foo(1, c=2)
foo 1 None 2 None
>>> foo(a =1, b=3)
foo 1 3 None None
>>> foo(1)
Traceback (most recent call last):
  File "<pyshell#136>", line 1, in <module>
    foo(1)
  File "<pyshell#132>", line 4, in foo
    raise ValueError("1 and only 1 of b,c, d parameters required")
ValueError: 1 and only 1 of b,c, d parameters required
>>> foo(1, b=2, c=3)
Traceback (most recent call last):
  File "<pyshell#137>", line 1, in <module>
    foo(1, b=2, c=3)
  File "<pyshell#132>", line 4, in foo
    raise ValueError("1 and only 1 of b,c, d parameters required")
ValueError: 1 and only 1 of b,c, d parameters required
>>> foo(a=1, d=None)
Traceback (most recent call last):
  File "<pyshell#138>", line 1, in <module>
    foo(a=1, d=None)
  File "<pyshell#132>", line 4, in foo
    raise ValueError("1 and only 1 of b,c, d parameters required")
ValueError: 1 and only 1 of b,c, d parameters required