生成器作为函数参数 [英] Generator as function argument

查看:44
本文介绍了生成器作为函数参数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

如果我们有:

def f(*args):打印成功!"打印参数

  1. 这可以正常工作.

    <预><代码>>>>f(1, *[2])成功!(1, 2)

  2. 这不符合预期.

    <预><代码>>>>f(*[2], 1)文件

  3. 这可以正常工作

    <预><代码>>>>f(1 代表 [1] 中的 x,*[2])成功!(生成器对象 在 0x7effe06bdcd0>,2)

  4. 这有效,但我不明白为什么.它不应该以与 2) 相同的方式失败

    <预><代码>>>>f(*[2], 1 代表 [1] 中的 x)成功!(生成器对象 在 0x7effe06bdcd0>,2)

解决方案

3. 和 4.应该在所有 Python 版本上都是语法错误.发现了一个影响 Python 2.5 - 3.4 版本的错误,该错误随后被发布到 Python 问题跟踪器.由于这个错误,如果一个没有括号的生成器表达式只伴随着 *args 和/或 **kwargs,它就会被接受作为函数的参数.虽然 Python 2.6+ 允许情况 3. 和 4.,但 Python 2.5 只允许情况 3. - 但它们都反对 文档化语法:

call ::= primary "(" [argument_list [","]|表达式geneexpr_for] ")"

即文档说一个函数调用包括 primary(计算结果为可调用的表达式),然后是括号中的 参数列表 只是一个无括号的生成器表达式;并且在参数列表中,所有生成器表达式都必须在括号中.

<小时>

这个错误(虽然它似乎不为人所知)已在 Python 3.5 预发布版本中修复.在 Python 3.5 中,生成器表达式周围总是需要括号,除非它是函数的唯一参数:

Python 3.5.0a4+(默认:a3f2b171b765,2015 年 5 月 19 日,16:14:41)[GCC 4.9.2] 在 Linux 上输入帮助"、版权"、信用"或许可"以获取更多信息.>>>f(1 for i in [42], *a)文件<stdin>",第 1 行SyntaxError:如果不是唯一参数,则必须将生成器表达式加括号

这现在记录在 Python 3.5 中的新功能,感谢 DeTeReR 发现了这个错误.

<小时>

漏洞分析

对 Python 2.6 进行了更改,其中 允许使用关键字参数 after *args:

<块引用>

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

<预><代码>>>>def f(*args, **kw):...打印参数,千瓦...>>>f(1,2,3, *(4,5,6), 关键字=13)(1, 2, 3, 4, 5, 6) {'关键字': 13}

以前这可能是语法错误.(阿毛里供稿)忘记 d'Arc;问题 3473.)

<小时>

然而,Python 2.6 语法 没有区分关键字参数,位置参数,或裸生成器表达式 - 它们都是解析器的 argument 类型.

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

for (i = 0; i 

然而,这个函数根本考虑*args——它专门只查找普通的位置参数和关键字参数.

在同一个函数的更深处,为非关键字生成了错误信息关键字 arg 后的 arg:

if (TYPE(ch) == 参数) {expr_ty e;如果 (NCH(ch) == 1) {如果(n关键字){ast_error(CHILD(ch, 0),"关键字 arg 后的非关键字 arg");返回空;}...

但这同样适用于 not 没有括号的生成器表达式的参数,如 else if 语句证明:

else if (TYPE(CHILD(ch, 1)) == gen_for) {e = ast_for_genexp(c, ch);如果(!e)返回空;asdl_seq_SET(args, nargs++, e);}

因此允许无括号的生成器表达式跳过.

<小时>

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

arglist: 参数 (',' 参数)* [',']

参数:( test [comp_for] |测试 '=' 测试 |'**' 测试 |'*' 测试 )

并且 for 循环已更改

for (i = 0; i 

从而修复了错误.

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

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

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

*args**kwargs 之前的无括号生成器现在停止工作.

<小时>

为了定位这个bug,我尝试了各种Python版本.在 2.5 中你会得到 SyntaxError:

Python 2.5.5(r255:77872,2010 年 11 月 28 日,16:43:48)[GCC 4.4.5] 在 linux2 上输入帮助"、版权"、信用"或许可"以获取更多信息.>>>f(*[1], 2 代表 [2] 中的 x)文件<stdin>",第 1 行f(*[1], 2 代表 [2] 中的 x)

这在 Python 3.5 的一些预发布之前得到了修复:

Python 3.5.0a4+(默认:a3f2b171b765,2015 年 5 月 19 日,16:14:41)[GCC 4.9.2] 在 Linux 上输入帮助"、版权"、信用"或许可"以获取更多信息.>>>f(*[1], 2 代表 [2] 中的 x)文件<stdin>",第 1 行SyntaxError:如果不是唯一参数,则必须将生成器表达式加括号

但是,括号中的生成器表达式在 Python 3.5 中有效,但在 Python 3.4 中无效:

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

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

<预><代码>>>>打印(*范围(5),42)0 1 2 3 4 42

所以实际的错误(生成器使用 *star 没有括号)确实在 Python 3.5 中修复了,并且可以在 Python 3.4 之间的变化中找到该错误和 3.5

Can anyone explain why passing a generator as the only positional argument to a function seems to have special rules?

If we have:

def f(*args):
    print "Success!"
    print args

  1. This works, as expected.

    >>> f(1, *[2])
    Success!
    (1, 2)
    

  2. This does not work, as expected.

    >>> f(*[2], 1)
      File "<stdin>", line 1
    SyntaxError: only named arguments may follow *expression
    

  3. This works, as expected

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

  4. This works, but I don't understand why. Shouldn't it fail in the same way as 2)

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

解决方案

Both 3. and 4. should be syntax errors on all Python versions. However you've found a bug that affects Python versions 2.5 - 3.4, and which was subsequently 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] ")"

i.e. the documentation says a function call comprises of primary (the expression that evaluates to a callable), followed by, in parentheses, either an argument list or just an unparenthesized generator expression; and within the argument list, all generator expressions must be in parentheses.


This bug (though it seems it had not been known), had been fixed in Python 3.5 prereleases. In Python 3.5 parentheses are always required around a generator expression, unless it is the only argument to the function:

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

This is now documented in the What's New in Python 3.5, thanks to DeTeReR spotting this bug.


Analysis of the bug

There was a change made to Python 2.6 which 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.)


However, the Python 2.6 grammar does not make any distinction between keyword arguments, positional arguments, or bare generator expressions - they are all of type argument to the parser.

As per Python rules, a generator expression must be parenthesized if it is not the sole argument to the function. This is validated in the 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;
}

However this function does not consider the *args at all - it specifically only looks for ordinary positional arguments and keyword arguments.

Further down in the same function, there is an error message generated for 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;
        }
        ...

But this again applies to arguments that are not unparenthesized generator expressions as 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);
}

Thus an unparenthesized generator expression was allowed to slip pass.


Now in Python 3.5 one can use the *args anywhere in a function call, so the Grammar was changed to accommodate for this:

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

and

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

and the for loop was changed to

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++;
    }
}

Thus fixing the bug.

However the inadvertent change is that the valid looking constructions

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

and

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

where an unparenthesized generator precedes *args or **kwargs now stopped working.


To locate this bug, I tried various Python versions. In 2.5 you'd get 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])

And this was fixed before some prerelease of 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

However, the parenthesized generator expression, it works in Python 3.5, but it does not work not in Python 3.4:

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

And this is the clue. In Python 3.5 the *splatting is generalized; you can use it anywhere in a function call:

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

So the actual bug (generator working with *star without parentheses) was indeed fixed in Python 3.5, and the bug could be found in that what changed between Python 3.4 and 3.5

这篇关于生成器作为函数参数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆