2009-09-12 110 views
51

我来自静态语言的背景。有人可以解释(理想情况下通过实例)真正的世界使用** kwargs优于命名参数为什么在Python中使用** kwargs?与使用命名参数相比,有哪些真实世界的优点?

对我来说,它似乎只是使函数调用更模糊。谢谢。

+1

用C可变参数的想法 - 有时你无法知道你的论点是什么。 – 2009-09-13 05:48:23

+10

请不要使用“现实世界”这个词 - 它是有争议的。它说“到目前为止,我所见过的所有例子都是人为和无用的,我的应用程序是现实世界,你的例子是一个幻想世界。”请改变你的问题以消除“真实世界”。 – 2009-09-13 12:53:37

+67

只有喜欢争论的人会认为我的问题是争议性的。 – meppum 2009-10-02 02:38:20

回答

32

真实世界的例子:

装饰 - 他们通常是通用的,所以你不能指定前期的观点:

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

哪里的地方你想用未知数量的关键字参数来做魔术。 Django的ORM这样做,例如:

Model.objects.filter(foo__lt = 4, bar__iexact = '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中的plot选项传递给一个通用绘图函数,该函数接受这些选项作为命名参数。 – Tristan 2009-09-12 18:47:43

+1

虽然我会考虑那种不好的风格,因为它阻碍了内省。 – bayer 2009-09-12 19:13:47

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文档:局部是 '相对等效' 来这个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 
+0

这里,因为'keywords'是一个字典,不.copy()只是创建另一个指针?这意味着'fkeywords'被添加到原始字典中。我想你会想使用copy.deepcopy()来创建字典的实际副本,不是吗? – aeroNotAuto 2011-11-09 17:44:34

+0

*'currying'*是*'部分参数绑定'的街道名称* – smci 2013-08-16 09:36:34

51

由于一系列原因,您可能想要接受几乎任意命名的参数 - 这就是**kw表单允许您执行的操作。

最常见的原因是将参数直接传递给你正在包装的其他函数(装饰器是这种情况的一种情况,但FAR来自唯一的一种情况!) - 在这种情况下,**kw放松了包装和wrappee,因为包装者不必知道或关心所有wrappee的论点。下面是另一个完全不同的原因:

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

如果所有的名字不得不提前知道的话,显然这种做法简直不存在的,对不对?而且顺便说一句,在适用时,我更喜欢做一个字典的键是文字字符串,以这种方式:

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

仅仅是因为后者是相当标点符号重,因此不易阅读。

当接受**kwargs的优秀理由都不适用时,请不要接受它:这很简单。 IOW,如果没有充足的理由允许调用者传递具有任意名称的额外命名参数,请不要让这种情况发生 - 请避免在def语句中的函数签名末尾添加**kw表单。

由于使用**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,在这种情况下生活将是绝对的地狱!

+0

+1 - 如果您习惯于(说)java,那么可以做到这一点,但这种做法有一定的范式转变。 – ConcernedOfTunbridgeWells 2009-09-12 20:29:22

10

有两种常见的情况:

第一条:您是包装另外的功能,这需要一些关键字参数,但你只是要通过他们:

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' 
31

你可能想使用**kwargs另一个原因(和*args)是如果你要扩展一个子类中的现有方法。您想通过现有的所有参数到父类的方法,但要确保你的类继续工作,即使在未来的版本中签名的变化:

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

+1:非常非常频繁地这样做。 – 2009-09-13 12:54:10

2

这里有一个例子,我在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()))