79

我来自静态语言背景。有人可以解释(最好通过示例)使用 **kwargs 对命名参数的现实世界优势吗?

对我来说,这似乎只会使函数调用更加模棱两可。谢谢。

4

8 回答 8

70

出于一系列原因,您可能希望接受几乎任意命名的参数——这就是**kw表单允许您做的事情。

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

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

如果必须提前知道所有名称,那么显然这种方法就不可能存在,对吧?顺便说一句,如果适用,我更喜欢这种方式来制作一个其键是文字字符串的字典:

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

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

当接受的所有理由都不**kwargs适用时,请不要接受:就这么简单。IOW,如果没有充分的理由允许调用者传递具有任意名称的额外命名参数,请不要允许这种情况发生 - 只需避免在语句**kw中函数签名的末尾放置一个表单。def

至于在调用中使用 **kw,这使您可以将必须​​传递的确切命名参数集(每个都有相应的值)放在一个字典中,独立于单个调用点,然后在单个调用点使用该字典。比较:

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

到:

if x:
  if y:
    f(x=x, y=y)
  else:
    f(x=x)
else:
  if y:
    f(y=y)
  else:
    f()

即使只有两种可能性(并且是最简单的一种!),缺乏**kw已经使第二种选择绝对站不住脚和无法容忍——想象一下当有六种可能性时它会如何发挥作用,可能在稍微丰富的交互中.. . 没有**kw,在这种情况下,生活将是绝对的地狱!

于 2009-09-12T18:52:09.237 回答
44

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

class MySubclass(Superclass):
    def __init__(self, *args, **kwargs):
        self.myvalue = kwargs.pop('myvalue', None)
        super(MySubclass, self).__init__(*args, **kwargs)
于 2009-09-12T19:15:33.167 回答
40

现实世界的例子:

装饰器——它们通常是通用的,所以你不能预先指定参数:

def decorator(old):
    def new(*args, **kwargs):
        # ...
        return old(*args, **kwargs)
    return new

您想用未知数量的关键字参数做魔术的地方。Django 的 ORM 就是这样做的,例如:

Model.objects.filter(foo__lt = 4, bar__iexact = 'bar')
于 2009-09-12T18:50:27.017 回答
14

有两种常见情况:

首先:您正在包装另一个函数,该函数需要一些关键字参数,但您只是要传递它们:

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

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

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'
于 2009-09-12T18:56:22.170 回答
4

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

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

这是一个示例,我在 CGI Python 中使用。我创建了一个**kwargs使用该__init__功能的类。这使我可以使用类在服务器端模拟 DOM:

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

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

Div(class = 'foo')

解决方案是访问底层字典。

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

我并不是说这是该功能的“正确”用法。我要说的是,有各种不可预见的方式可以使用此类功能。

于 2009-09-12T19:49:47.600 回答
1

这是另一个典型的例子:

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

def proclaim(object_, message, data):
    print(MESSAGE.format(**locals()))
于 2009-09-13T05:30:16.683 回答
0

一个例子是实现python-argument-binders,使用如下:

>>> 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 文档:partial 与此 impl '相对等效':

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
于 2009-09-12T18:50:59.567 回答