generadores - what are python generators used for
Generador como argumento de funciĆ³n (1)
Tanto 3. como 4.
deben
ser errores de sintaxis en todas las versiones de Python.
Sin embargo, ha encontrado un error que afecta a las versiones de Python 2.5 - 3.4, y que posteriormente se
publicó en el rastreador de problemas de Python
.
Debido al error, se aceptaba una expresión generadora sin paréntesis como argumento de una función si solo estaba acompañada por
*args
y / o
**kwargs
.
Mientras que Python 2.6+ permitió ambos casos 3. y 4., Python 2.5 permitió solo el caso 3. - sin embargo, ambos estaban en contra de la
gramática documentada
:
call ::= primary "(" [argument_list [","]
| expression genexpr_for] ")"
es decir, la documentación dice que una llamada a la función se compone de
primary
(la expresión que se evalúa como invocable), seguida de, entre paréntesis, una lista de argumentos
o
simplemente una expresión generadora sin paréntesis;
y dentro de la lista de argumentos, todas las expresiones generadoras deben estar entre paréntesis.
Este error (aunque parece que no se conocía), se ha solucionado en Python 3.5 prereleases. En Python 3.5, siempre se requieren paréntesis alrededor de una expresión generadora, a menos que sea el único argumento para la función:
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
Esto ahora está documentado en Novedades en Python 3.5 , gracias a que DeTeReR detectó este error.
Análisis del error
Se realizó un cambio en Python 2.6 que
permitió el uso de argumentos de palabras clave
después de
*args
:
También es legal proporcionar argumentos de palabras clave después de un argumento * args a una llamada de función.
>>> def f(*args, **kw): ... print args, kw ... >>> f(1,2,3, *(4,5,6), keyword=13) (1, 2, 3, 4, 5, 6) {''keyword'': 13}
Anteriormente, esto habría sido un error de sintaxis. (Contribución de Amaury Forgeot d''Arc; número 3473.)
Sin embargo, la
grammar
Python 2.6 no distingue entre argumentos de palabras clave, argumentos posicionales o expresiones de generador desnudas; todos son
argument
de tipo para el analizador.
Según las reglas de Python, una expresión generadora debe estar entre paréntesis si no es el único argumento de la función.
Esto se valida en
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;
}
Sin embargo, esta función
no
considera en absoluto los argumentos
*args
, solo busca específicamente argumentos posicionales y argumentos de palabras clave.
Más abajo en la misma función, se genera un mensaje de error para el argumento sin palabras clave después de la palabra clave 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;
}
...
Pero esto se aplica nuevamente a los argumentos que
no
son expresiones generadoras
sin
paréntesis como lo
demuestra la declaración
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);
}
Por lo tanto, se dejó pasar una expresión de generador sin paréntesis.
Ahora en Python 3.5 uno puede usar los
*args
en cualquier parte de una llamada de función, por lo que la
Grammar
se cambió para adaptarse a esto:
arglist: argument ('','' argument)* ['','']
y
argument: ( test [comp_for] |
test ''='' test |
''**'' test |
''*'' test )
y el
bucle
for
se cambió
a
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++;
}
}
Así arreglando el error.
Sin embargo, el cambio involuntario es que las construcciones de aspecto válido
func(i for i in [42], *args)
y
func(i for i in [42], **kwargs)
donde un generador sin paréntesis precede a
*args
o
**kwargs
ahora dejó de funcionar.
Para localizar este error, probé varias versiones de Python.
En 2.5 obtendrías
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])
Y esto se solucionó antes de una versión preliminar de 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
Sin embargo, la expresión del generador entre paréntesis, funciona en Python 3.5, pero no funciona en Python 3.4:
f(*[1], (2 for x in [2]))
Y esta es la pista.
En Python 3.5, el
*splatting
está generalizado;
puede usarlo en cualquier lugar de una llamada de función:
>>> print(*range(5), 42)
0 1 2 3 4 42
Entonces, el error real (generador que funciona con
*star
sin paréntesis)
se
corrigió en Python 3.5, y el error se pudo encontrar en lo que cambió entre Python 3.4 y 3.5
¿Alguien puede explicar por qué pasar un generador como el único argumento posicional de una función parece tener reglas especiales?
Si tenemos:
>>> def f(*args):
>>> print "Success!"
>>> print args
-
Esto funciona, como se esperaba.
>>> f(1, *[2]) Success! (1, 2)
-
Esto no funciona, como se esperaba.
>>> f(*[2], 1) File "<stdin>", line 1 SyntaxError: only named arguments may follow *expression
-
Esto funciona, como se esperaba
>>> f(1 for x in [1], *[2]) Success! (generator object <genexpr> at 0x7effe06bdcd0>, 2)
-
Esto funciona, pero no entiendo por qué. ¿No debería fallar de la misma manera que 2)
>>> f(*[2], 1 for x in [1]) Success! (generator object <genexpr> at 0x7effe06bdcd0>, 2)