我具有以下功能
def foo(a, b=None, c=None, d=None)
其中可选参数b
,c
和d
中,只有一个应设置为数值(不允许为零)。该函数还应该接受其他可选参数,为清楚起见,我在定义中将其省略。
要对此进行检查,我会得到如下参数值和名称的列表:
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)]
。我不明白为什么d
和c
在此列表中,因为它们的值是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()
似乎与问题有关,但我不明白出了什么问题以及如何解决此问题。
答案 0 :(得分:1)
出了什么问题
每次创建局部变量时,locals()
词典都会更新。特别是,在调用enumerate(locals().keys())
之后和调用locals().values())[i]
之前,您将创建局部变量i
和arg
。因此,前者返回的决定与后者返回的决定具有不同的成员资格。
类似地,在您的for
循环中:
for i, arg in enumerate(locals().keys()):
在第一次迭代期间,locals()
仅具有a
,b
,c
,d
。在第二次迭代中,它还具有i
和arg
。
以及如何解决此问题。
选择一种更简单的方式来解释您的论点。我可以建议:
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
以及恰好一个附加参数传递给该函数,否则赋值将失败。附加参数的名称必须为b
,c
,d
中的一个,否则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