生成器作为函数参数


81

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

如果我们有:

>>> 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)
    

@丹 f((*[2, 3]), 1)在给出语法错误*-您能否进一步说明您的建议?另外,问题不是“如何使其工作”,而是“为什么会这样工作?”
J0HN 2015年

1
不是一个确切的重复,但颇为相似:stackoverflow.com/questions/12720450/...。TL; DR似乎是一个实现细节-就像那样工作。
J0HN 2015年

2
注意:案例2应该在python 3.5+中运行(由于PEP 448
Bakuriu 2015年

1
Python 3.5已经发布,它现在告诉案例3(实际上也是案例4)已修复。Python 3.5的新增功能
Antti Haapala 2015年

Answers:


76

3.和4.在所有Python版本上均应为语法错误。但是,您发现了一个影响Python 2.5-3.4的错误,该错误随后发布到Python问题跟踪器中。由于该错误,如果仅使用*args和/或,则将无括号的生成器表达式作为函数的参数接受**kwargs。尽管Python 2.6+允许3和4.两种情况,但Python 2.5仅允许3和3的情况。-但它们都违背了已记录的语法

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

现在,这已记录在Python 3.5的新增功能中,这要归功于DeTeReR发现了此错误。


错误分析

有这对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}

以前,这可能是语法错误。(由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;
}

但是,此函数根本考虑,*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;
        }
        ...

但这再次适用于不是非括号生成器表达式的自变量,如以下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);
}

因此,允许使用非括号的生成器表达式滑移传递。


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

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

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

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

从而修复错误。

但是无意的改变是有效的外观结构

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

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


1
它在3.5中不固定-只需将括号放在生成器周围,行为是相同的。
viraptor 2015年

1
@viraptor好点,在3.4中带括号的表达式给出了一个错误
Antti Haapala 2015年

?? 在3.4.3上运行:f(*[1], 1 for x in [1])=>(<generator object <genexpr> at 0x7fa56c889288>, 1)
viraptor

@viraptorf(*[1], (1 for x in [1]))是Python 3.4上的语法错误。在Python 3.5中有效。
Antti Haapala

如果可以的话,我会为这个答案加点金,感谢您提供相关的C源代码!
Nick Sweeting
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.