生成器作为函数参数

Generator as function argument

谁能解释为什么将生成器作为函数的唯一位置参数传递似乎有特殊规则?

如果我们有:

def f(*args):
    print "Success!"
    print args
  1. 如预期的那样有效。

    >>> f(1, *[2])
    Success!
    (1, 2)
    
  2. 正如预期的那样,这不起作用。

    >>> f(*[2], 1)
      File "<stdin>", line 1
    SyntaxError: only named arguments may follow *expression
    
  3. 正如预期的那样有效

    >>> f(1 for x in [1], *[2])
    Success! 
    (generator object <genexpr> at 0x7effe06bdcd0>, 2)
    
  4. 这行得通,但我不明白为什么。它不应该以与 2)

    相同的方式失败吗?
    >>> f(*[2], 1 for x in [1])
    Success!
    (generator object <genexpr> at 0x7effe06bdcd0>, 2)
    

3. 和 4.应该 在所有 Python 版本上都是语法错误。 但是你找到了一个影响 Python 版本 2.5 - 3.4 的错误,随后 posted to the Python issue tracker. Because of the bug, an unparenthesized generator expression was accepted as an argument to a function if it was accompanied only by *args and/or **kwargs. While Python 2.6+ allowed both cases 3. and 4., Python 2.5 allowed only case 3. - yet both of them were against the documented grammar:

call    ::=     primary "(" [argument_list [","]
                            | expression genexpr_for] ")"

即文档说函数调用包括 primary (计算为可调用的表达式),后跟括号中的 或者 参数列表 只是一个未加括号的生成器表达式; 在参数列表中,所有生成器表达式都必须放在括号中。


这个错误(尽管它似乎不为人知)已在 Python 3.5 预发布版中修复。在 Python 3.5 中,生成器表达式始终需要括号,除非它是函数的唯一参数:

Python 3.5.0a4+ (default:a3f2b171b765, May 19 2015, 16:14:41) 
[GCC 4.9.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> f(1 for i in [42], *a)
  File "<stdin>", line 1
SyntaxError: Generator expression must be parenthesized if not sole argument

这现在记录在 What's New in Python 3.5 中,感谢 DeTeReR 发现了这个错误。


bug分析

对 Python 2.6 进行了更改,其中 allowed the use of keyword arguments after *args:

It’s also become legal to provide keyword arguments after a *args argument to a function call.

>>> def f(*args, **kw):
...     print args, kw
...
>>> f(1,2,3, *(4,5,6), keyword=13)
(1, 2, 3, 4, 5, 6) {'keyword': 13}

Previously this would have been a syntax error. (Contributed by Amaury Forgeot d’Arc; issue 3473.)


然而,Python 2.6 grammar 对关键字参数、位置参数或纯生成器表达式没有任何区别 - 它们对解析器来说都是 argument 类型。

根据 Python 规则,如果生成器表达式不是函数的唯一参数,则必须用括号括起来。这在 Python/ast.c:

中得到验证
for (i = 0; i < NCH(n); i++) {
    node *ch = CHILD(n, i);
    if (TYPE(ch) == argument) {
        if (NCH(ch) == 1)
            nargs++;
        else if (TYPE(CHILD(ch, 1)) == gen_for)
            ngens++;
        else
            nkeywords++;
    }
}
if (ngens > 1 || (ngens && (nargs || nkeywords))) {
    ast_error(n, "Generator expression must be parenthesized "
              "if not sole argument");
    return NULL;
}

但是这个函数 根本 不考虑 *args - 它专门只查找普通的位置参数和关键字参数。

在同一函数的更下方,为 non-keyword arg after keyword arg 生成了一条错误消息:

if (TYPE(ch) == argument) {
    expr_ty e;
    if (NCH(ch) == 1) {
        if (nkeywords) {
            ast_error(CHILD(ch, 0),
                      "non-keyword arg after keyword arg");
            return NULL;
        }
        ...

但这再次适用于 不是 未加括号的生成器表达式的参数,如 evidenced by the else if statement:

else if (TYPE(CHILD(ch, 1)) == gen_for) {
    e = ast_for_genexp(c, ch);
    if (!e)
        return NULL;
    asdl_seq_SET(args, nargs++, e);
}

因此允许未加括号的生成器表达式通过。


现在在 Python 3.5 中,可以在函数调用的任何地方使用 *args,所以 Grammar 已更改以适应以下情况:

arglist: argument (',' argument)*  [',']

argument: ( test [comp_for] |
            test '=' test |
            '**' test |
            '*' test )

for loop was changed

for (i = 0; i < NCH(n); i++) {
    node *ch = CHILD(n, i);
    if (TYPE(ch) == argument) {
        if (NCH(ch) == 1)
            nargs++;
        else if (TYPE(CHILD(ch, 1)) == comp_for)
            ngens++;
        else if (TYPE(CHILD(ch, 0)) == STAR)
            nargs++;
        else
            /* TYPE(CHILD(ch, 0)) == DOUBLESTAR or keyword argument */
            nkeywords++;
    }
}

因此修复了错误。

然而,无意中的变化是看起来有效的结构

func(i for i in [42], *args)

func(i for i in [42], **kwargs)

*args**kwargs 之前未加括号的生成器现在停止工作。


为了找到这个错误,我尝试了各种 Python 版本。在 2.5 中你会得到 SyntaxError:

Python 2.5.5 (r255:77872, Nov 28 2010, 16:43:48) 
[GCC 4.4.5] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> f(*[1], 2 for x in [2])
  File "<stdin>", line 1
    f(*[1], 2 for x in [2])

这在 Python 3.5:

的某些预发布之前已修复
Python 3.5.0a4+ (default:a3f2b171b765, May 19 2015, 16:14:41) 
[GCC 4.9.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> f(*[1], 2 for x in [2])
  File "<stdin>", line 1
SyntaxError: Generator expression must be parenthesized if not sole argument

但是,带括号的生成器表达式,它在 Python 3.5 中有效,但在 Python 3.4 中不起作用:

f(*[1], (2 for x in [2]))

这就是线索。在 Python 3.5 中 *splatting 被泛化;您可以在函数调用的任何地方使用它:

>>> print(*range(5), 42)
0 1 2 3 4 42

所以实际的错误(使用 *star 不带括号的生成器) 确实在 Python 3.5 中得到修复,并且可以在其中找到错误Python 3.4 和 3.5

之间发生了什么变化