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

Proper way to use **kwargs in Python

当涉及到默认值时,Python中使用**kwargs的正确方法是什么?

kwargs返回一个字典,但是设置默认值的最佳方法是什么?我应该把它当作字典来使用吗?使用get函数?

1
2
3
4
class ExampleClass:
    def __init__(self, **kwargs):
        self.val = kwargs['val']
        self.val2 = kwargs.get('val2')

这是一个简单的问题,但是我找不到很好的资源。在我所见过的代码中,人们用不同的方式来做这件事,而且很难知道该使用什么。


对于不在字典中的键,可以将默认值传递给get():

1
self.val2 = kwargs.get('val2',"default value")

但是,如果您计划使用具有特定缺省值的特定参数,为什么不首先使用命名参数呢?

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


虽然大多数答案是这样说的,例如,

1
2
3
4
def f(**kwargs):
    foo = kwargs.pop('foo')
    bar = kwargs.pop('bar')
    ...etc...

是"一样的"

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

这不是真的。在后一种情况下,f可以被调用为f(23, 42),而前一种情况只接受命名参数——没有位置调用。通常,您希望为调用者提供最大的灵活性,因此第二种形式(正如大多数answers断言的那样)更可取:但情况并非总是如此。当您接受许多可选参数(通常只传递少数参数)时,强制使用命名参数(threading.Thread就是一个例子)可能是一个很好的主意(避免意外和调用站点上的代码不可读!)。第一种形式是如何在python2中实现它。

这个习惯用法非常重要,以至于在Python 3中它现在有了特殊的支持语法:def签名中的单个*之后的每个参数都是关键字,也就是说,不能作为位置参数传递,而只能作为命名参数传递。所以在python3中,你可以把上面的代码写成:

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

实际上,在python3中,甚至可以有一些关键字参数,它们是不可选的(没有默认值的参数)。

然而,Python 2仍然有很长的生产周期,所以最好不要忘记让您在Python 2中实现重要设计思想的技术和习惯用法,这些设计思想是Python 3语言直接支持的!


我建议这样做

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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


你会做

1
self.attribute = kwargs.pop('name', default_value)

1
self.attribute = kwargs.get('name', default_value)

如果使用pop,则可以检查是否发送了任何假值,并采取适当的操作(如果有的话)。


使用**kwargs和默认值很容易。然而,有时您不应该首先使用**kwargs。

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

1
2
3
4
class ExampleClass( object ):
    def __init__(self, **kwargs):
        self.val = kwargs.get('val',"default1")
        self.val2 = kwargs.get('val2',"default2")

以上是一个"何苦呢?"的声明。它是一样的

1
2
3
4
class ExampleClass( object ):
    def __init__(self, val="default1", val2="default2"):
        self.val = val
        self.val2 = val2

当您使用**kwargs时,您的意思是一个关键字不仅是可选的,而且是有条件的。有比简单的默认值更复杂的规则。

当您使用**kwargs时,您通常指的是以下内容,其中不应用简单的默认值。

1
2
3
4
5
6
7
8
9
10
11
12
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" )


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

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


这是另一个方法:

1
2
3
4
5
6
7
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)


跟进@srhegde使用setattr的建议:

1
2
3
4
5
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列表中包含所有项时,这种变体非常有用。


我认为在Python中使用默认值**kwargs的正确方法是使用dictionary方法setdefault,如下图所示:

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

这样,如果用户在关键字args中传递"val"或"val2",就会使用它们;否则,将使用已设置的默认值。


你可以这样做

1
2
3
4
5
6
class ExampleClass:
    def __init__(self, **kwargs):
        arguments = {'val':1, 'val2':2}
        arguments.update(kwargs)
        self.val = arguments['val']
        self.val2 = arguments['val2']

如果您想要将它与*args组合在一起,您必须在定义的末尾保留*args和**kwargs。

所以:

1
2
3
def method(foo, bar=None, *args, **kwargs):
    do_something_with(foo, bar)
    some_other_function(*args, **kwargs)


@AbhinavGupta和@Steef建议使用update(),我发现这对处理大的参数列表非常有帮助:

1
args.update(kwargs)

如果我们想检查用户是否传递了任何虚假的/不受支持的参数呢?@VinaySajip指出,可以使用pop()迭代处理参数列表。那么,任何剩下的争论都是假的。好了。

下面是另一种可能的方法,它保持了使用update()的简单语法:

1
2
3
4
5
6
7
8
9
10
# kwargs = dictionary of user-supplied arguments
# args = dictionary containing default arguments

# Check that user hasn't given spurious arguments
unknown_args = user_args.keys() - default_args.keys()
if unknown_args:
    raise TypeError('Unknown arguments: {}'.format(unknown_args))

# Update args to contain user-supplied arguments
args.update(kwargs)

unknown_args是一个set,包含默认情况下没有出现的参数名。


**kwargs允许添加任意数量的关键字参数。一个人可能有一个键列表,他可以为其设置默认值。但是,为不确定数量的键设置默认值似乎没有必要。最后,将键作为实例属性可能很重要。所以,我将这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Person(object):
listed_keys = ['name', 'age']

def __init__(self, **kwargs):
    _dict = {}
    # Set default values for listed keys
    for item in self.listed_keys:
        _dict[item] = 'default'
    # Update the dictionary with all kwargs
    _dict.update(kwargs)

    # Have the keys of kwargs as instance attributes
    self.__dict__.update(_dict)

处理未知或多个参数的另一个简单解决方案是:

1
2
3
4
5
6
7
8
9
10
class ExampleClass(object):

    def __init__(self, x, y, **kwargs):
      self.x = x
      self.y = y
      self.attributes = kwargs

    def SomeFunction(self):
      if 'something' in self.attributes:
        dosomething()