TL;TR 寻找将位置和关键字参数解包为基于简单规范(例如名称列表)的有序位置参数序列的习语和模式。这个想法似乎类似于scanf样式的解析。
我正在封装Python模块someapi
的函数。
someapi
的函数只接受位置参数,在大多数情况下是痛苦的数字。
我希望能够让调用者以灵活的方式传递参数给我的包装器。
以下是我想允许的包装器调用示例:
# foo calls someapi.foo()
foo(1, 2, 3, 4)
foo(1, 2, 3, 4, 5) # but forward only 1st 4 to someapi.foo
foo([1, 2, 3, 4])
foo([1, 2, 3, 4, 5, 6]) # but forward only 1st 4 to someapi.foo
foo({'x':1, 'y':2, 'z':3, 'r':4})
foo(x=1, y=2, z=3, r=4)
foo(a=0, b=0, x=1, y=2, z=3, r=4) # but forward only x,y,z,r someapi.foo
我不认为有必要支持混合定位和关键词参数的复杂情况:
foo(3, 4, x=1, y=2)
这是我第一次尝试为调用someapi.foo
的foo
包装器实现此类参数处理:
def foo(*args, **kwargs):
# BEGIN arguments un/re-packing
a = None
kwa = None
if len(args) > 1:
# foo(1, 2, 3, 4)
a = args
elif len(args) == 1:
if isinstance(args[0], (list, tuple)) and len(args[0]) > 1:
# foo([1, 2, 3, 4])
a = args[0]
if isinstance(args[0], dict):
# foo({'x':1, 'y':2, 'z':3, 'r':4})
kwa = args[0]
else:
# foo(x=1, y=2, z=3, r=4)
kwa = kwargs
if a:
(x, y, z, r) = a
elif kwa:
(x, y, z, r) = (kwa['x'], kwa['y'], kwa['z'], kwa['r'])
else:
raise ValueError("invalid arguments")
# END arguments un/re-packing
# make call forwarding unpacked arguments
someapi.foo(x, y, z, r)
我认为这个程序已经按照预期完成了任务,但是有两个问题:
- 我能否用更符合Python惯用法的方式来实现它?
- 我需要包装数十个
someapi
函数,如何避免在每个包装器中复制和调整整个BEGIN/END块?
对于问题1,我不知道答案。
然而,我尝试解决问题2。
因此,我定义了一个基于names
的简单规范的通用参数处理程序。
names
根据实际的包装器调用指定了一些内容:
- 从
*args
中解包多少个参数?(请参见下面的len(names)
测试) **kwargs
中期望哪些关键字参数?(请参见下面返回元组的生成器表达式)
这是新版本:
def unpack_args(names, *args, **kwargs):
a = None
kwa = None
if len(args) >= len(names):
# foo(1, 2, 3, 4...)
a = args
elif len(args) == 1:
if isinstance(args[0], (list, tuple)) and len(args[0]) >= len(names):
# foo([1, 2, 3, 4...])
a = args[0]
if isinstance(args[0], dict):
# foo({'x':1, 'y':2, 'z':3, 'r':4...})
kwa = args[0]
else:
# foo(x=1, y=2, z=3, r=4)
kwa = kwargs
if a:
return a
elif kwa:
if all(name in kwa.keys() for name in names):
return (kwa[n] for n in names)
else:
raise ValueError("missing keys:", \
[name for name in names if name not in kwa.keys()])
else:
raise ValueError("invalid arguments")
这使我能够按照以下方式实现包装函数:
def bar(*args, **kwargs):
# arguments un/re-packing according to given of names
zargs = unpack_args(('a', 'b', 'c', 'd', 'e', 'f'), *args, **kwargs)
# make call forwarding unpacked arguments
someapi.bar(*zargs)
我认为我已经实现了比上面的foo
版本更多的优点:
使调用者具有所需的灵活性。
紧凑的形式,减少复制和粘贴。
位置参数的灵活协议:
bar
可以使用7、8个或更多位置参数或长列表进行调用,但只考虑前6个。例如,它将允许迭代处理长列表的数字(例如,考虑几何坐标):
# meaw expects 2 numbers
n = [1,2,3,4,5,6,7,8]
for i in range(0, len(n), 2):
meaw(n[i:i+2])
- 灵活的关键字参数协议:可以指定比实际使用更多的关键词,或者字典中可以有比使用更多的项。
回到上面的问题1,我能做得更好并使其更符合Pythonic吗?
此外,我想请求对我的解决方案进行审查:是否存在任何错误?我是否忽略了什么?如何改进它?
meaw
函数的示例,以说明其中一种用例。因此,这并不是忽略参数,而是将其视为隐式切片。当然,必须记录文档,以便我的包装器的用户知道这种功能。 - mloskotfoo(x=1, yismissing=2, z=3, r=4)
,他将会得到一个 KeyError。不确定这里的预期行为是什么。 - KurzedMetalfoo(* [1,2,3,4,5])
或foo(** {'x':1,'y':2})
不会太繁琐,并可以减少代码和文档的需求。 - sgillies