【问题标题】:Semantics of tuple unpacking in pythonpython中元组拆包的语义
【发布时间】:2012-05-23 17:56:09
【问题描述】:

为什么python在函数调用中只允许命名参数跟随元组解包表达式?

>>> def f(a,b,c):
...     print a, b, c
... 
>>> f(*(1,2),3)
  File "<stdin>", line 1
SyntaxError: only named arguments may follow *expression

这仅仅是一种审美选择,还是在某些情况下允许这样做会导致一些歧义?

【问题讨论】:

  • 你可以在解包之前将最后一个参数添加到元组中。
  • 一个有趣的问题。我似乎记得围绕 python 2.5 更改的规则 - 你曾经 必须*args**kwargs 放在最后,但我找不到更改日志条目。

标签: python tuples iterable-unpacking


【解决方案1】:

我很确定人们“自然”不喜欢这个的原因是因为它使后面的论点的含义模棱两可,具体取决于插值序列的长度:

def dangerbaby(a, b, *c):
    hug(a)
    kill(b) 

>>> dangerbaby('puppy', 'bug')
killed bug
>>> cuddles = ['puppy']
>>> dangerbaby(*cuddles, 'bug')
killed bug
>>> cuddles.append('kitten')
>>> dangerbaby(*cuddles, 'bug')
killed kitten

仅查看对dangerbaby 的最后两个调用,您无法分辨哪一个按预期工作,哪一个杀死了小猫绒毛。

当然,在最后进行插值时,也存在一些不确定性。但混淆仅限于插值序列 - 它不会影响其他参数,例如 bug

[我快速搜索了一下是否可以找到任何官方信息。似乎 varags 的 * 前缀是 introduced in python 0.9.8。之前的语法是discussed here,它的工作规则相当复杂。由于在没有 * 标记时添加额外的参数“必须”发生在最后,所以似乎只是延续了。最后有一个mention here 不是通过电子邮件就参数列表进行的长时间讨论。]

【讨论】:

    【解决方案2】:

    我怀疑这是为了与函数定义中的星号保持一致,毕竟这是函数调用中星号的模型。

    在下面的定义中,参数*c 将吞噬所有后续的非关键字参数,因此很明显,当调用f 时,为d 传递值的唯一方法将作为关键字参数。

    def f(a, b, *c, d=1):
        print "slurped", len(c)
    

    (这种“仅关键字参数”仅在 Python 3 中受支持。在 Python 2 中,无法在带星号的参数之后分配值,因此上述内容是非法的。)

    因此,在函数定义中,带星号的参数必须跟在所有普通的位置参数之后。您观察到的是,相同的规则已扩展到函数调用。这样,星型语法对于函数声明和函数调用是一致的。

    另一个并行性是在函数调用中只能有一个(单)星号参数。以下是非法的,尽管人们很容易想象它是被允许的。

    f(*(1,2), *(3,4))
    

    【讨论】:

    • @Jeff,您显然使用的是 python 2,它没有仅关键字参数。更新以说明这一点。
    • 对不起!我猜仍然没有发现 python 2 和 3 之间的所有差异。
    • 删除了我上面的(无用的)评论。
    • 嗯,它帮助我为其他人解决了这个问题,非常感谢!
    • 禁止f(*(1, 2), *(3, 4)) 之类的一个原因是相应的f(**{'a': 1}, **{'b': 2}) 可能会导致f(**{'a': 1}, **{'a': 2}) 之类的歧义。
    【解决方案3】:

    首先,使用包装函数自己提供一个非常相似的接口很简单:

    def applylast(func, arglist, *literalargs):
      return func(*(literalargs + arglist))
    
    applylast(f, (1, 2), 3)  # equivalent to f(3, 1, 2)
    

    其次,增强解释器以原生支持您的语法可能会增加函数应用程序对性能非常关键的活动的开销。即使它只需要编译代码中的一些额外指令,由于这些例程的高使用率,这可能构成不可接受的性能损失,以换取用户库中不经常调用且容易容纳的特性。

    【讨论】:

      【解决方案4】:

      一些观察:

      1. Python 在关键字参数之前处理位置参数(您的示例中的f(c=3, *(1, 2)) 仍然打印1 2 3)。这是有道理的,因为 (i) 函数调用中的大多数参数都是位置参数,并且 (ii) 编程语言的语义需要明确(即,需要根据处理位置参数和关键字参数的顺序进行选择) )。
      2. 如果我们在函数调用中确实有一个位于右侧的位置参数,则很难定义它的含义。如果我们调用f(*(1, 2), 3),应该是f(1, 2, 3) 还是f(3, 1, 2),为什么任何一个选择都比另一个更有意义?
      3. 对于官方解释,PEP 3102 提供了很多关于函数定义如何工作的见解。函数定义中的星号(*)表示位置参数的结束(规范部分)。要了解原因,请考虑:def g(a, b, *c, d)。除了作为关键字参数(位置参数将被c“抓取”)之外,无法为d 提供值。
      4. 重要的是要意识到这意味着什么:星号标志着位置参数的结束,这意味着所有位置参数都必须位于该位置或左侧。

      【讨论】:

        【解决方案5】:

        改变顺序:

        def f(c,a,b):
            print(a,b,c)
        f(3,*(1,2))
        

        【讨论】:

        • 这是一个规则,参数应该遵循这个顺序:(args,name=args,*args,**args)
        • 是的,但是为什么这是一个规则?
        • 因为如果你可以按任何顺序做事,那么当人们以任何方式编写东西时,就很难遵循代码,那么你可能不使用 Python。换句话说,因为 Guido 这么说,这就是为什么:)
        • @Karmel,我很好奇为什么函数参数必须按此顺序排列。我已经阅读了一些 PEP 邮件列表讨论,并且 Guido 做出的大多数决定都是出于非常好的、具体和实际的原因,而不仅仅是“因为他这么说”。
        • @user545424 实际上有些是“因为他这么说”,比如字典迭代。遍历字典会遍历键,有些人认为它应该是键、对值,但他说仅遍历键是他的直觉,通常他稍后会为他的直觉找到充分的理由。这是来自 Google IO 的一次谈话,我忘了是哪一次。
        【解决方案6】:

        如果你有一个 Python 3 关键字参数,比如

        def f(*a, b=1):
            ...
        

        那么你可能会期望像f(*(1, 2), 3) 这样将a 设置为(1 , 2) 并将b 设置为3,但当然,即使你想要的语法被允许,它也不会,因为关键字-只有参数必须是关键字,如@9​​87654327@。如果允许,我想它必须将a 设置为(1, 2, 3) 并将b 保留为默认1。所以它可能不是语法上的歧义,而是预期的歧义,这是 Python 极力避免的。

        【讨论】:

          猜你喜欢
          • 2014-11-20
          • 1970-01-01
          • 2012-04-22
          • 2018-04-26
          • 2020-05-12
          • 1970-01-01
          • 2015-08-02
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多