83 votes

Générateur comme argument de fonction

Quelqu'un peut-il expliquer pourquoi le passage d'un générateur comme le seul de position argument à une fonction semble avoir des règles spéciales?

Si nous avons:

>>> def f(*args):
>>>    print "Success!"
>>>    print args
  1. Cela fonctionne comme prévu.

    >>> f(1, *[2])
    Success!
    (1, 2)
    
  2. Cela ne fonctionne pas comme il le devrait.

    >>> f(*[2], 1)
      File "<stdin>", line 1
    SyntaxError: only named arguments may follow *expression
    
  3. Cela fonctionne comme prévu

    >>> f(1 for x in [1], *[2])
    Success! 
    (generator object <genexpr> at 0x7effe06bdcd0>, 2)
    
  4. Cela fonctionne, mais je ne comprends pas pourquoi. Ne devrait-il pas l'échec de la même manière que 2)

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

79voto

Antti Haapala Points 11542

3. et 4. devrait être des erreurs de syntaxe sur toutes les versions de Python. Cependant, vous avez trouvé un bug qui affecte les versions de Python 2.5 - 3.4, et qui a par la suite été publié pour le Python issue tracker. En raison du bogue, sans parenthèse générateur d'expression a été accepté comme un argument à une fonction, si elle a été accompagnée que par *args et/ou **kwargs. Alors que la version 2.6 de Python+ a permis à la fois de cas 3. et 4., Python 2.5 autorisé seulement 3 de la casse. - pourtant, les deux d'entre eux étaient contre le documentée de la grammaire:

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

c'est à dire la documentation dit d'un appel de fonction se compose d' primary (l'expression qui correspond à un callable), suivie, entre parenthèses, soit une liste d'arguments ou juste un sans parenthèse générateur d'expression; et dans la liste d'arguments, tous générateur d'expressions entre parenthèses.


Ce bug (mais il semble qu'il n'avait pas été connu), avait été fixé en Python 3.5 prereleases. En Python 3.5 parenthèses sont toujours nécessaires autour d'un générateur d'expression, à moins qu'il est le seul argument à la fonction:

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

C'est maintenant documenté dans les nouveautés de Python 3.5, grâce à DeTeReR repérage de ce bug.


L'analyse de la bogue

Il n'y avait qu'une modification apportée à la version 2.6 de Python qui a permis l'utilisation du mot-clé arguments après *args:

Il est également devenu morale de fournir des arguments mot-clé après un *args argument d'un appel de fonction.

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

Auparavant, cela aurait été une erreur de syntaxe. (Contribué par Amaury Forgeot d'Arc; question 3473.)


Cependant, la version 2.6 de Python de la grammaire ne fait aucune distinction entre le mot-clé arguments, les arguments de position, ou nu générateur d'expressions - ils sont tous de type argument de l'analyseur.

Comme par Python règles, un générateur d'expression doit être mise entre parenthèses, si ce n'est pas le seul argument à la fonction. C'est validé dans l' 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;
}

Cependant, cette fonction ne pas considérer l' *args - plus particulièrement, elle ne regarde que pour le commun des arguments de position et les arguments mots-clefs.

Plus bas dans la même fonction, il y a un message d'erreur généré par la non-mot-clé arg après le mot-clé 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;
        }
        ...

Mais cela s'applique à nouveau à des arguments qui ne sont pas sans parenthèse générateur d'expressions comme en témoigne l' else if déclaration:

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

Donc un sans parenthèse générateur d'expression a été autorisé à se glisser passer.


Maintenant en Python 3.5, on peut utiliser le *args n'importe où dans un appel de fonction, donc la Grammaire a été modifié pour tenir compte de ceci:

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

et

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

et l' for boucle a été changé à

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

Ce qui permet de corriger le bug.

Toutefois, la modification par inadvertance, c'est que la validité de la recherche de constructions

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

et

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

lorsqu'un sans parenthèse générateur précède *args ou **kwargs maintenant cessé de travailler.


Pour trouver ce bug, j'ai essayé différentes versions de Python. Dans la version 2.5, vous obtiendrez 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])

Et cela a été fixée avant certains préliminaire 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

Cependant, la mise entre parenthèses du générateur d'expression, il fonctionne en Python 3.5, mais il ne fonctionne pas en Python 3.4:

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

Et c'est la moindre idée. En Python 3.5 *splatting est généralisée; vous pouvez l'utiliser n'importe où dans un appel de fonction:

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

La bogue (générateur de travailler avec des *star sans parenthèses) était en effet fixé en Python 3.5, et le bug a pu être trouvé en ce que ce qui a changé entre Python 3.4 et 3.5

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X