Generator作为函数参数

DeT*_*ReR 81 python syntax generator-expression python-2.7

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

如果我们有:

>>> def f(*args):
>>>    print "Success!"
>>>    print args
Run Code Online (Sandbox Code Playgroud)
  1. 这正如预期的那样有效.

    >>> f(1, *[2])
    Success!
    (1, 2)
    
    Run Code Online (Sandbox Code Playgroud)
  2. 按预期,这不起作用.

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

    >>> f(1 for x in [1], *[2])
    Success! 
    (generator object <genexpr> at 0x7effe06bdcd0>, 2)
    
    Run Code Online (Sandbox Code Playgroud)
  4. 这有效,但我不明白为什么.不应该以与2)相同的方式失败

    >>> f(*[2], 1 for x in [1])                                               
    Success!
    (generator object <genexpr> at 0x7effe06bdcd0>, 2)
    
    Run Code Online (Sandbox Code Playgroud)

Ant*_*ala 75

3.和4. 都应该是所有Python版本的语法错误.但是,您发现了一个影响Python版本2.5 - 3.4的错误,随后发布到Python问题跟踪器中.由于该错误,如果函数只附带*args和/或,则接受一个未表示的生成器表达式作为函数的参数**kwargs.虽然Python 2.6+允许情况3和4.但Python 2.5仅允许情况3. - 但它们都反对记录的语法:

call    ::=     primary "(" [argument_list [","]
                            | expression genexpr_for] ")"
Run Code Online (Sandbox Code Playgroud)

即,文档说一个函数调用包括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
Run Code Online (Sandbox Code Playgroud)

由于DeTeReR发现了这个错误,现在在Python 3.5的新功能中记录了一点.


分析错误

有这对Python 2.6所做的更改允许使用的关键字参数 *args:

在函数调用的*args参数之后提供关键字参数也是合法的.

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

以前这可能是语法错误.(供稿人:Amaury Forgeot d'Arc;第3473期.)


但是,Python 2.6 语法没有对关键字参数,位置参数或裸生成器表达式进行任何区分 - 它们都是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;
}
Run Code Online (Sandbox Code Playgroud)

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

在同一个函数中,在关键字arg之后为非关键字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;
        }
        ...
Run Code Online (Sandbox Code Playgroud)

但是,这又适用于有观点作为加括号的发电机表情证明了else if声明:

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

因此,允许未表达的发生器表达滑过.


现在在Python 3.5中,可以使用*args函数调用中的任何位置,因此更改了语法以适应此:

arglist: argument (',' argument)*  [',']
Run Code Online (Sandbox Code Playgroud)

argument: ( test [comp_for] |
            test '=' test |
            '**' test |
            '*' test )
Run Code Online (Sandbox Code Playgroud)

并将for循环更改

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++;
    }
}
Run Code Online (Sandbox Code Playgroud)

从而修复bug.

然而,无意中的改变是有效的外观结构

func(i for i in [42], *args)
Run Code Online (Sandbox Code Playgroud)

func(i for i in [42], **kwargs)
Run Code Online (Sandbox Code Playgroud)

未加模糊的发电机先于*args**kwargs现在停止工作的地方.


为了找到这个bug,我尝试了各种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])
Run Code Online (Sandbox Code Playgroud)

这在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
Run Code Online (Sandbox Code Playgroud)

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

f(*[1], (2 for x in [2]))
Run Code Online (Sandbox Code Playgroud)

这就是线索.在Python 3.5中,它*splatting是一般化的; 你可以在函数调用中的任何地方使用它:

>>> print(*range(5), 42)
0 1 2 3 4 42
Run Code Online (Sandbox Code Playgroud)

所以,真正的Bug(发电机工作*star没有括号)确实固定在Python 3.5,这个bug会在找到的Python 3.4和3.5之间发生了什么变化