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

发布于 2024-08-04 09:31:49 字数 108 浏览 6 评论 0原文

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

对我来说,这似乎只会使函数调用更加模糊。谢谢。

I come from a background in static languages. Can someone explain (ideally through example) the real world advantages of using **kwargs over named arguments?

To me it only seems to make the function call more ambiguous. Thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

窗影残 2024-08-11 09:31:49

您可能出于一系列原因想要接受几乎任意的命名参数——而这正是 **kw 形式可以让您做到的。

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

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

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

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

仅仅因为后者的标点符号非常多,因此可读性较差。

当接受 **kwargs 的充分理由都不适用时,就不要接受它:就这么简单。 IOW,如果没有充分的理由允许调用者传递具有任意名称的额外命名参数,请不要允许这种情况发生——只需避免在函数末尾放置 **kw 形式即可def 语句中的签名。

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

You may want to accept nearly-arbitrary named arguments for a series of reasons -- and that's what the **kw form lets you do.

The most common reason is to pass the arguments right on to some other function you're wrapping (decorators are one case of this, but FAR from the only one!) -- in this case, **kw loosens the coupling between wrapper and wrappee, as the wrapper doesn't have to know or care about all of the wrappee's arguments. Here's another, completely different reason:

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

if all the names had to be known in advance, then obviously this approach just couldn't exist, right? And btw, when applicable, I much prefer this way of making a dict whose keys are literal strings to:

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

simply because the latter is quite punctuation-heavy and hence less readable.

When none of the excellent reasons for accepting **kwargs applies, then don't accept it: it's as simple as that. IOW, if there's no good reason to allow the caller to pass extra named args with arbitrary names, don't allow that to happen -- just avoid putting a **kw form at the end of the function's signature in the def statement.

As for using **kw in a call, that lets you put together the exact set of named arguments that you must pass, each with corresponding values, in a dict, independently of a single call point, then use that dict at the single calling point. Compare:

if x: kw['x'] = x
if y: kw['y'] = y
f(**kw)

to:

if x:
  if y:
    f(x=x, y=y)
  else:
    f(x=x)
else:
  if y:
    f(y=y)
  else:
    f()

Even with just two possibilities (and of the very simplest kind!), the lack of **kw is aleady making the second option absolutely untenable and intolerable -- just imagine how it plays out when there half a dozen possibilities, possibly in slightly richer interaction... without **kw, life would be absolute hell under such circumstances!

枉心 2024-08-11 09:31:49

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

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

Another reason you might want to use **kwargs (and *args) is if you're extending an existing method in a subclass. You want to pass all the existing arguments onto the superclass's method, but want to ensure that your class keeps working even if the signature changes in a future version:

class MySubclass(Superclass):
    def __init__(self, *args, **kwargs):
        self.myvalue = kwargs.pop('myvalue', None)
        super(MySubclass, self).__init__(*args, **kwargs)
半边脸i 2024-08-11 09:31:49

现实世界的示例:

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

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

您想要使用未知数量的关键字参数进行魔法的地方。 Django 的 ORM 就是这样做的,例如:

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

Real-world examples:

Decorators - they're usually generic, so you can't specify the arguments upfront:

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

Places where you want to do magic with an unknown number of keyword arguments. Django's ORM does that, e.g.:

Model.objects.filter(foo__lt = 4, bar__iexact = 'bar')
演出会有结束 2024-08-11 09:31:49

有两种常见情况:

第一:您正在包装另一个带有多个关键字参数的函数,但您只是将它们传递:

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'

There are two common cases:

First: You are wrapping another function which takes a number of keyword argument, but you are just going to pass them along:

def my_wrapper(a, b, **kwargs):
    do_something_first(a, b)
    the_real_function(**kwargs)

Second: You are willing to accept any keyword argument, for example, to set attributes on an object:

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'
乱世争霸 2024-08-11 09:31:49

如果您事先不知道参数的名称,**kwargs 会很好。例如,dict 构造函数使用它们来初始化新字典的键。

dict(**kwargs) ->;使用名称=值对初始化的新字典
    在关键字参数列表中。例如:dict(一=1,二=2)
In [3]: dict(one=1, two=2)
Out[3]: {'one': 1, 'two': 2}

**kwargs are good if you don't know in advance the name of the parameters. For example the dict constructor uses them to initialize the keys of the new dictionary.

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}
方圜几里 2024-08-11 09:31:49

这是我在 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'})

我并不是说这是该功能的“正确”用法。我想说的是,有各种不可预见的方式可以使用此类功能。

Here's an example, I used in CGI Python. I created a class that took **kwargs to the __init__ function. That allowed me to emulate the DOM on the server-side with classes:

document = Document()
document.add_stylesheet('style.css')
document.append(Div(H1('Imagist\'s Page Title'), id = 'header'))
document.append(Div(id='body'))

The only problem is that you can't do the following, because class is a Python keyword.

Div(class = 'foo')

The solution is to access the underlying dictionary.

Div(**{'class':'foo'})

I'm not saying that this is a "correct" usage of the feature. What I'm saying is that there are all kinds of unforseen ways in which features like this can be used.

清风无影 2024-08-11 09:31:49

这是另一个典型的例子:

MESSAGE = "Lo and behold! A message {message!r} came from {object_} with data {data!r}."

def proclaim(object_, message, data):
    print(MESSAGE.format(**locals()))

And here's another typical example:

MESSAGE = "Lo and behold! A message {message!r} came from {object_} with data {data!r}."

def proclaim(object_, message, data):
    print(MESSAGE.format(**locals()))
故事与诗 2024-08-11 09:31:49

一个例子是实现 python-argument-binders,如下使用:

<前><代码>>>>从 functools 导入部分
>>>>> def f(a, b):
...返回a+b
>>>>> p = 部分(f, 1, 2)
>>>>> p()
3
>>>>> p2 = 部分(f, 1)
>>>>> p2(7)
8

这是来自 functools.partial python 文档:部分与此实现“相对等效”:

def 部分(func, *args, **关键字):
    def newfunc(*fargs, **fkeywords):
        新关键字 = keywords.copy()
        newkeywords.update(fkeywords)
        return func(*(args + fargs), **新关键字)
    newfunc.func = 函数
    newfunc.args = 参数
    newfunc.keywords = 关键字
    返回新函数

One example is implementing python-argument-binders, used like this:

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

This is from the functools.partial python docs: partial is 'relatively equivalent' to this 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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文