【问题标题】:Why use **kwargs in python? What are some real world advantages over using named arguments?为什么在 python 中使用 **kwargs?与使用命名参数相比,现实世界有哪些优势?
【发布时间】:2009-09-12 18:38:01
【问题描述】:

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

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

【问题讨论】:

  • 想想 C 中的可变参数——有时你不知道你的参数是什么。

标签: python keyword-argument


【解决方案1】:

出于一系列原因,您可能希望接受几乎任意命名的参数 - 这就是 **kw 表单可以让您做的事情。

最常见的原因是将参数直接传递给您要包装的其他函数(装饰器是这种情况的一种,但远非唯一!)——在这种情况下,**kw 放松了耦合在 wrapper 和 wrappee 之间,因为 wrapper 不必知道或关心 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,在这种情况下,生活将是绝对的地狱!

【讨论】:

  • +1 - 能够做到这一点非常有用,但如果你习惯于(比如)java,这将是一种范式转变。
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

    现实世界的例子:

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

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

    您想用未知数量的关键字参数变魔术的地方。 Django 的 ORM 就是这样做的,例如:

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

    【讨论】:

    • “想变魔术”是什么意思。 magic 在这里是什么意思?另外,我将 magic 与糟糕的设计联系起来。 Django案例设计合理吗?
    【解决方案4】:

    常见的有两种情况:

    首先:您正在包装另一个函数,该函数接受多个关键字参数,但您只是要传递它们:

    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'
    

    【讨论】:

      【解决方案5】:

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

      【讨论】:

      • 如果您想将大量参数传递给另一个您不一定知道选项的函数,则通常使用它。例如,specialplot(a,**kwargs) 可以将 kwargs 中的绘图选项传递给通用绘图函数,该函数接受这些选项作为命名参数。
      • 虽然我认为这种风格很糟糕,因为它阻碍了内省。
      【解决方案6】:

      这是一个例子,我在 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'})
      

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

      【讨论】:

        【解决方案7】:

        还有一个典型的例子:

        MESSAGE = "Lo and behold! A message {message!r} came from {object_} with data {data!r}."
        
        def proclaim(object_, message, data):
            print(MESSAGE.format(**locals()))
        

        【讨论】:

          【解决方案8】:

          一个例子是实现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 文档:partial 与这个 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
          

          【讨论】:

          • 这里,因为keywords 是一个字典, .copy() 不只是创建另一个指针吗?这意味着 fkeywords 将被添加到原始字典中。我认为您想使用 copy.deepcopy() 创建字典的实际副本,不是吗?
          • 'currying''partial argument binding' 的街道名称
          猜你喜欢
          • 1970-01-01
          • 2018-11-07
          • 1970-01-01
          • 1970-01-01
          • 2010-09-24
          • 2010-12-27
          • 2015-04-06
          • 2013-05-09
          • 2017-05-29
          相关资源
          最近更新 更多