为什么在python中使用**kwargs?与使用命名参数相比,真实世界有哪些优势?

Why use **kwargs in python? What are some real world advantages over using named arguments?

我来自一个静态语言的背景。有人能解释(理想情况下是通过例子)在现实世界中使用**Kwargs比命名参数的优势吗?

在我看来,这只会使函数调用变得更加模糊。谢谢。


由于一系列原因,您可能希望接受几乎任意命名的参数——这正是**kw形式允许您这样做的。

最常见的原因是将参数直接传递给您要包装的其他函数(修饰符是这种情况的一种,但远不是唯一的一种!)--在这种情况下,**kw会松开包装器和wrappee之间的耦合,因为包装器不必知道或关心wrappee的所有参数。另一个完全不同的原因是:

1
d = dict(a=1, b=2, c=3, d=4)

如果所有的名字都必须提前知道,那么显然这种方法是不存在的,对吗?顺便说一句,在适用的情况下,我更喜欢用这种方式来制作一个键是文字字符串的dict:

1
d = {'a': 1, 'b': 2, 'c': 3, 'd': 4}

只是因为后者标点符号很重,因此可读性较差。

当接受**kwargs的最好理由都不适用时,就不要接受它:就这么简单。iow,如果没有好的理由允许调用者传递带有任意名称的额外命名参数,那么不要允许这种情况发生——只需避免在def语句中的函数签名末尾放置**kw表单。

至于在调用中使用**kw,它允许您将必须传递的一组精确命名参数(每个参数都有相应的值)放在dict中,独立于单个调用点,然后在单个调用点使用该dict。比较:

1
2
3
if x: kw['x'] = x
if y: kw['y'] = y
f(**kw)

到:

1
2
3
4
5
6
7
8
9
10
if x:
  if y:
    f(x=x, y=y)
  else:
    f(x=x)
else:
  if y:
    f(y=y)
  else:
    f()

即使只有两种可能(也是最简单的一种!)缺少EDOCX1[2]是为了让第二个选项绝对无法维持和不可容忍——想象一下当有六种可能性,可能是在稍微丰富的交互中,它是如何发挥作用的……没有以东,在这种情况下,生活将是地狱!


您可能想要使用**kwargs*args的另一个原因是,如果您要扩展子类中的现有方法。您希望将所有现有参数传递到超类的方法上,但要确保类继续工作,即使签名在未来版本中发生更改:

1
2
3
4
class MySubclass(Superclass):
    def __init__(self, *args, **kwargs):
        self.myvalue = kwargs.pop('myvalue', None)
        super(MySubclass, self).__init__(*args, **kwargs)


现实世界示例:

装饰器-它们通常是通用的,因此不能预先指定参数:

1
2
3
4
5
def decorator(old):
    def new(*args, **kwargs):
        # ...
        return old(*args, **kwargs)
    return new

使用未知数量的关键字参数进行魔术的地方。Django的ORM会这样做,例如:

1
Model.objects.filter(foo__lt = 4, bar__iexact = 'bar')

有两种常见情况:

首先:您要包装另一个接受许多关键字参数的函数,但您只需传递它们:

1
2
3
def my_wrapper(a, b, **kwargs):
    do_something_first(a, b)
    the_real_function(**kwargs)

第二:您愿意接受任何关键字参数,例如,在对象上设置属性:

1
2
3
4
5
6
7
8
class OpenEndedObject:
    def __init__(self, **kwargs):
        for k, v in kwargs.items():
            setattr(self, k, v)

foo = OpenEndedObject(a=1, foo='bar')
assert foo.a == 1
assert foo.foo == 'bar'

如果您事先不知道参数的名称,那么**kwargs是很好的。例如,dict构造函数使用它们来初始化新字典的键。

1
2
dict(**kwargs) -> new dictionary initialized with the name=value pairs
    in the keyword argument list.  For example:  dict(one=1, two=2)
1
2
In [3]: dict(one=1, two=2)
Out[3]: {'one': 1, 'two': 2}


下面是一个例子,我在cgi python中使用过。我创建了一个类,将**kwargs带到__init__函数。这允许我在服务器端用类来模拟DOM:

1
2
3
4
document = Document()
document.add_stylesheet('style.css')
document.append(Div(H1('Imagist\'s Page Title'), id = 'header'))
document.append(Div(id='body'))

唯一的问题是您不能执行以下操作,因为class是一个python关键字。

1
Div(class = 'foo')

解决方案是访问基础字典。

1
Div(**{'class':'foo'})

我不是说这是功能的"正确"用法。我要说的是,有各种各样的不可见的方式可以使用像这样的功能。


下面是另一个典型的例子:

1
2
3
4
MESSAGE ="Lo and behold! A message {message!r} came from {object_} with data {data!r}."

def proclaim(object_, message, data):
    print(MESSAGE.format(**locals()))

一个例子是实现python参数绑定,如下所示:

1
2
3
4
5
6
7
8
9
>>> from functools import partial
>>> def f(a, b):
...     return a+b
>>> p = partial(f, 1, 2)
>>> p()
3
>>> p2 = partial(f, 1)
>>> p2(7)
8

这是来自functools.partial python docs:partial与此impl"相对等效":

1
2
3
4
5
6
7
8
9
def partial(func, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = keywords.copy()
        newkeywords.update(fkeywords)
        return func(*(args + fargs), **newkeywords)
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc