ChatGPT解决这个技术问题 Extra ChatGPT

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

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

kwargs 返回一个字典,但设置默认值的最佳方法是什么,或者有没有?我应该将它作为字典访问吗?使用get函数?

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

一个简单的问题,但我找不到好的资源。人们在我见过的代码中以不同的方式进行操作,很难知道该使用什么。


b
balpha

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

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

但是,如果您打算使用具有特定默认值的特定参数,为什么不首先使用命名参数呢?

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

我喜欢仅将位置参数用于必需的参数,将 kwargs 用于可能指定或未指定的参数,但使用默认值会很有帮助。 kwargs 很好,因为您可以按照您选择的任何顺序提交您的参数。立场论点不会给你那种自由。
您可以按您喜欢的任何顺序传递命名参数。如果您不使用名称,您只需要遵守职位 - 在 kwargs 的情况下,您必须这样做。相反,使用命名参数而不是 kwargs 为您提供了不使用名称的额外自由 - 但是,您必须保持顺序。
@Kekoa:您始终可以按您选择的任何顺序提交命名参数。您不必使用 **kwargs 来获得这种灵活性。
pylint 将其标记为在 __init__() 中使用 kwargs 的错误形式。有人可以解释为什么这是一个值得一提的违法行为吗?
A
Alex Martelli

虽然大多数答案都在说,例如,

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

是相同的”

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 语言中得到支持!


@Alex Martelli:我还没有找到一个声称 kwargs 与命名参数相同的答案,更不用说优越了。但是对 Py3k 的好讨论发生了变化,所以 +1
@Alex Martelli:非常感谢您的回答,它让我了解到 python 3 允许强制关键字参数,缺少这些参数通常会导致我的代码和函数中的架构不令人满意。
A
Abhinav Gupta

我建议这样的事情

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 以覆盖任何重复的键。


谢谢@AbhinavGupta!正是我想要的。刚刚添加了 for key in options: self.__setattr__(key, options[key]),我可以开始了。 :)
V
Vinay Sajip

你会做的

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

或者

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

如果您使用 pop,那么您可以检查是否发送了任何虚假值,并采取适当的措施(如果有)。


您能否通过建议 .pop 帮助您“检查是否发送了任何虚假值”来澄清您的意思?
@Alan H.:如果在完成所有弹出操作后 kwargs 中还有任何东西,那么您就会得到虚假值。
@VinaySajip:好的,这是 .pop "vs" .get 的一个好点,但我仍然不明白为什么 pop 比命名参数更可取,除了强制调用者不使用位置参数。
@MestreLion:这取决于您的 API 允许多少关键字参数。我并没有声称我的建议比命名参数更好,但 Python 允许您在 kwargs 中捕获未命名参数是有原因的。
所以,只是检查。如果键存在,pop 是否返回字典值,如果不存在则返回传递的 default_value?然后删除该密钥?
S
S.Lott

使用 **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" )

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

既然参数个数未知时使用 **kwargs,为什么不这样做呢?

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

是的,这是优雅而强大的......虽然不太确定acceptable_keys_list周围的方括号:我将其设为元组或列表,然后将这些方括号放在“if”语句中
对于需要所有键的情况,我稍作修改:stackoverflow.com/questions/1098549/…
o
orokusaki

这是另一种方法:

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 CBV 中经常使用(例如 get_form_kwargs())。 ccbv.co.uk/projects/Django/1.5/django.views.generic.edit/…
get_form() 方法展示了如何通过推迟到另一个方法(如上所述的 get_form_kwargs)广泛地获取关键字参数。它按如下方式实例化表单:form_class(**self.get_form_kwargs())
然后很容易在子类视图中覆盖 get_form_kwargs() 并根据特定逻辑添加/删除 kwargs。但这是针对 Django 教程的。
A
Arpit Solanki

我认为当涉及到默认值时,在 Python 中使用 **kwargs 的正确方法是使用字典方法 setdefault,如下所示:

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

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


C
Community

跟进使用 setattr@srhegde 建议:

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 列表中的所有项目时,此变体很有用。


这不是列表理解的用例,您应该在 init 方法中使用 for 循环。
S
Steef

你可以做这样的事情

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

J
Jens Timmerman

如果要将其与 *args 结合使用,则必须将 *args 和 **kwargs 保留在定义的末尾。

所以:

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

t
tmsss

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

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

u
user3503692

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

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)

u
user20160

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

args.update(kwargs)

如果我们想检查用户没有传递任何虚假/不受支持的参数怎么办? @VinaySajip 指出 pop() 可用于迭代处理参数列表。那么,任何剩余的论点都是虚假的。好的。

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

# 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,其中包含默认值中未出现的参数名称。