在Python中正确使用**kwargs的方法

602
在Python中,使用**kwargs时,有什么正确的方法来设置默认值呢? kwargs返回一个字典,但是设置默认值的最佳方法是什么,或者是否有一种方法?我应该只是将其作为字典来访问吗?使用get函数吗?
class ExampleClass:
    def __init__(self, **kwargs):
        self.val = kwargs['val']
        self.val2 = kwargs.get('val2')

人们在代码中以不同的方式进行操作,我见过很多种方法,很难知道该使用哪种方法。
14个回答

637
你可以为字典中不存在的键传递一个默认值给get():
self.val2 = kwargs.get('val2',"default value")

然而,如果您打算在特定默认值的情况下使用特定参数,为什么不首先使用命名参数呢?

def __init__(self, val2="default value", **kwargs):

27
我喜欢仅对必要的参数使用位置参数,对于可能或不可能指定的参数,我喜欢使用kwargs,并设置一个默认值。kwargs很好用,因为你可以按任意顺序提交参数。而位置参数则没有这种自由。 - Kekoa
105
你可以按任何顺序传递命名参数。如果你不使用名称,则只需要遵守位置 -- 对于kwargs的情况,你必须遵守位置。与kwargs相比,使用命名参数而不是kwargs给了你额外的自由,可以不使用名称 -- 然后,你必须保持顺序。 - balpha
14
你可以随意按任何顺序提交命名参数,不必使用**kwargs来获得此灵活性。 - S.Lott
15
为什么在__init__()中使用kwargs被pylint认为是不好的做法?有人能解释一下吗? - hughdbrown
2
@hughdbrown 可能是因为一个简单的 self.__dict__update(**kwargs) 可以重新定义方法并导致其他错误。 - Tobias Kienzler
显示剩余4条评论

307

尽管大多数答案都说,例如,

def f(**kwargs):
    foo = kwargs.pop('foo')
    bar = kwargs.pop('bar')
    ...etc...

“the same as”是什么意思

def f(foo=None, bar=None, **kwargs):
    ...etc...

这不是真的。在后一种情况下,f 可以被称为 f(23, 42),而前一种情况则仅接受命名参数 -- 不允许位置调用。通常您希望允许调用者最大的灵活性,因此第二种形式是更可取的: 但并非总是如此。当您接受许多可选参数,其中通常只有少数参数被传递时,强制使用命名参数可能是一个绝妙的主意(避免在调用方站点上发生意外和难以阅读的代码!)-- threading.Thread 就是一个例子。第一种形式是您在 Python 2 中实现它的方式。

这种习惯用法在 Python 3 中非常重要,它现在具有特殊的支持语法:在 def 签名中单个 * 后面的每个参数都是关键字参数,也就是说不能作为位置参数传递,只能作为命名参数传递。因此,在 Python 3 中,您可以将上述代码编写为:

def f(*, foo=None, bar=None, **kwargs):
    ...etc...

事实上,在Python 3中,你甚至可以有既没有默认值也不是可选的关键字参数。

然而,Python 2仍然有很长时间的生产力,所以最好不要忘记让你在Python 2中实现其重要设计思想的技巧和习惯用法,这些在Python 3中直接得到支持!


12
@Alex Martelli:我没有找到一个单一的答案声称kwargs与命名参数相同,更不用说是优越的了。但是对于Py3k变化的良好讨论,所以+1. - balpha
2
@Alex Martelli:非常感谢您的回答,它让我了解到Python 3允许强制关键字参数,这种缺乏通常会导致我的代码和函数架构不令人满意。 - FloW

105

我建议像这样做

def testFunc( **kwargs ):
    options = {
            'option1' : 'default_value1',
            'option2' : 'default_value2',
            'option3' : 'default_value3', }

    options.update(kwargs)
    print options

testFunc( option1='new_value1', option3='new_value3' )
# {'option2': 'default_value2', 'option3': 'new_value3', 'option1': 'new_value1'}

testFunc( option2='new_value2' )
# {'option1': 'default_value1', 'option3': 'default_value3', 'option2': 'new_value2'}

然后可以按照自己的需要使用这些值

dictionaryA.update(dictionaryB)dictionaryB 的内容添加到 dictionaryA 中,如果有重复的键,则会覆盖掉原有的键值。


6
谢谢@AbhinavGupta!正是我想要的。只需添加for key in options: self.__setattr__(key, options[key]),我就可以开始了。**: )** - propjk007

65

你需要这样做

self.attribute = kwargs.pop('name', default_value)
或者
self.attribute = kwargs.get('name', default_value)
如果你使用pop,那么你可以检查是否有任何不合适的值被发送,并采取适当的行动(如果需要)。

5
你能否说明一下,为什么建议使用.pop函数可以帮助你“检查是否有不正确的值被发送”? - Alan H.
17
如果在所有弹出操作完成后kwargs中还有剩余的内容,那么你就拥有了无效值。 - Vinay Sajip
@VinaySajip:好的,关于.pop和.get的比较,你提出了一个非常好的观点。但是除了强制调用者不使用位置参数之外,我仍然不明白为什么pop比命名参数更可取。 - MestreLion
1
@MestreLion:这取决于您的API允许多少关键字参数。我并不声称我的建议比命名参数更好,但Python允许您以kwargs的形式捕获未命名的参数是有原因的。 - Vinay Sajip
所以,只是检查一下。如果键存在,pop返回一个字典值,如果不存在,则返回传递的“default_value”?然后删除该键吗? - Daniel Möller

53

使用 **kwargs 和默认值很容易。但是有时候,你并不应该首先使用 **kwargs。

在这种情况下,我们并没有真正充分利用 **kwargs。

class ExampleClass( object ):
    def __init__(self, **kwargs):
        self.val = kwargs.get('val',"default1")
        self.val2 = kwargs.get('val2',"default2")
上面的声明是一个"无需费心"的声明。它与以下内容相同:
class ExampleClass( object ):
    def __init__(self, val="default1", val2="default2"):
        self.val = val
        self.val2 = val2

使用 **kwargs 时,表示关键字参数不仅是可选的,而且是有条件的。比简单的默认值更复杂的规则适用。

使用 **kwargs 时,通常意味着类似下面这样的情况,简单的默认值不适用。

class ExampleClass( object ):
    def __init__(self, **kwargs):
        self.val = "default1"
        self.val2 = "default2"
        if "val" in kwargs:
            self.val = kwargs["val"]
            self.val2 = 2*self.val
        elif "val2" in kwargs:
            self.val2 = kwargs["val2"]
            self.val = self.val2 / 2
        else:
            raise TypeError( "must provide val= or val2= parameter values" )

2
我喜欢那个小脑筋急转弯!我一直在想,“但是你可以使用get或pop与-哦,它们是相互依存的……” - trojjer

50

既然在参数数量未知的情况下使用了**kwargs,为什么不这样做呢?

class Exampleclass(object):
  def __init__(self, **kwargs):
    for k in kwargs.keys():
       if k in [acceptable_keys_list]:
          self.__setattr__(k, kwargs[k])

1
是的,这很优雅和强大...不过我对可接受键列表周围的方括号并不确定:我会将其作为元组或列表,然后在“if”语句中删除那些括号。 - mike rodent
我稍微修改了这个代码,以适应所有键都被期望的情况:https://dev59.com/IXNA5IYBdhLWcg3wEpoU#36336431 - rebelliard

19

这里有另一种方法:

def my_func(arg1, arg2, arg3):
    ... so something ...

kwargs = {'arg1': 'Value One', 'arg2': 'Value Two', 'arg3': 'Value Three'}
# Now you can call the function with kwargs like this:

my_func(**kwargs)

在Django CBVs中经常使用(例如get_form_kwargs())。http://ccbv.co.uk/projects/Django/1.5/django.views.generic.edit/FormView/ - trojjer
get_form() 方法展示了如何通过调用另一个方法(如上所述的 get_form_kwargs)来广泛获取关键字参数。它按照以下方式实例化表单:form_class(**self.get_form_kwargs()) - trojjer
在子类视图中覆盖get_form_kwargs()并根据特定逻辑添加/删除kwargs非常容易。但这是Django教程的内容。 - trojjer

19

我认为在Python中使用**kwargs时,正确的方式是使用字典方法setdefault来设置默认值,如下所示:

class ExampleClass:
    def __init__(self, **kwargs):
        kwargs.setdefault('val', value1)
        kwargs.setdefault('val2', value2)

通过这种方式,如果用户在关键字args中传递了'val'或'val2',则使用它们;否则,将使用已设置的默认值。


16

在跟进@srhegde建议使用setattr之后:

class ExampleClass(object):
    __acceptable_keys_list = ['foo', 'bar']

    def __init__(self, **kwargs):
        [self.__setattr__(key, kwargs.get(key)) for key in self.__acceptable_keys_list]

当预期类拥有我们acceptable列表中的所有项目时,此变体很有用。


2
这不是列表推导的使用情况,你应该在你的init方法中使用for循环。 - ettanany

14
你可以像这样做。
class ExampleClass:
    def __init__(self, **kwargs):
        arguments = {'val':1, 'val2':2}
        arguments.update(kwargs)
        self.val = arguments['val']
        self.val2 = arguments['val2']

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接