生成器作为函数参数

任何人都可以解释为什么传递一个生成器作为函数的唯一位置参数似乎有特殊的规则?

如果我们有:

>>> 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的bug,并且随后将其发布到Python问题跟踪器 。 由于这个bug,如果一个函数只有*args和/或**kwargs才会接受一个未被引用的生成器expression式作为函数的参数。 虽然Python 2.6+允许情况3和4,Python 2.5只允许情况3 – 但他们都反对logging的语法 :

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

即文档中说,函数调用包括primary (评估为可调用的expression式),后面是括号的参数列表或者仅仅是未经expression的生成器expression式; 在参数列表中,所有的生成器expression式都必须在括号内。


这个bug(虽然看起来还不知道)已经在Python 3.5预发行版中修复了。 在Python 3.5中,括号总是需要围绕生成器expression式,除非它是函数的唯一参数:

 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的新function中logging了,这要归功于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提供; issue 3473.)


但是,Python 2.6 语法不会在关键字参数,位置参数或裸露的生成器expression式之间进行区分 – 它们都是parsing器的typesargument

根据Python规则,如果不是函数的唯一参数,则必须将生成器expression式加括号。 这在Python/ast.c中进行了validation:

 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语句所certificate的非生成器生成器expression式的参数:

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

因此,一个未言语的发电机expression式被允许滑行。


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

从而修复这个bug。

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

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

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

在那里一个未经培训的发电机在*args**kwargs现在停止工作。


为了find这个错误,我尝试了各种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 

然而,括号内的生成器expression式,它在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 )确实已经在Python3.5中修复了,这个bug可以被发现,Python 3.4和3.5之间的变化