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


72

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

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


1
想想C语言中的varargs-有时您不知道参数将是什么。
查尔斯·达菲,2009年

Answers:


39

实际示例:

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

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

您想使用未知数量的关键字参数进行魔术操作的地方。Django的ORM可以做到这一点,例如:

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

“想做魔术”是什么意思?魔术在这里是什么意思?另外,我将魔术与不良设计联系起来。Django案例设计合理吗?
joel

65

您可能出于多种原因而接受几乎任意命名的参数-这就是 **kw表格允许您执行的操作。

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

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

如果必须事先知道所有名称,那么显然不存在这种方法,对吗?顺便说一句,在适用的情况下,我更喜欢这种以键为文字字符串的字典的方式:

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

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

如果没有一个很好的接受理由**kwargs,那么就不要接受:就这么简单。IOW,如果没有充分的理由允许调用者传递带有任意名称的额外的命名args,则不允许这种情况发生-只需避免**kwdef语句的函数签名末尾放置一个表单即可。

至于在调用中使用 **kw,它使您可以将必须​​传递的确切命名参数集(独立于单个调用点)组合在一个dict中,然后将其与每个单独的调用值一起使用,然后在单个调用点上使用该dict。比较:

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生活,在这种情况下绝对是地狱!


+1-能够做到这一点非常有用,但是如果您习惯使用(比如说)java,则可能会发生范式转变。
ConcernedOfTunbridgeWells 2009年

41

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

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

13

有两种常见情况:

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

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'

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}

1
如果您要将大量参数传递给不一定知道选项的其他函数,则通常使用它。例如,specialplot(a,** kwargs)可以将kwargs中的绘图选项传递给通用绘图函数,该函数接受这些选项作为命名参数。
Tristan

1
尽管我认为这种风格不好,但它会阻碍自省。
拜耳

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'})

我并不是说这是该功能的“正确”用法。我的意思是,有各种各样无法预料的方式可以使用这种功能。


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()))

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相对地与此隐喻:

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

在这里,由于keywords是字典,.copy()是否仅创建另一个指针?这意味着将fkeywords其添加到原始字典中。我认为您想使用copy.deepcopy()创建字典的实际副本,不是吗?
aeroNotAuto

“ currying”“部分参数绑定”
smci
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.