Generator jako argument funkcji

Czy ktoś może wyjaśnić, dlaczego przekazywanie generatora jako jedynego argumentu pozycyjnego do funkcji wydaje się mieć specjalne reguły?

Jeśli mamy:

>>> def f(*args):
>>>    print "Success!"
>>>    print args
  1. To działa zgodnie z oczekiwaniami.
    >>> f(1, *[2])
    Success!
    (1, 2)
    
  2. To nie działa, zgodnie z oczekiwaniami.

    >>> f(*[2], 1)
      File "<stdin>", line 1
    SyntaxError: only named arguments may follow *expression
    
  3. To działa, zgodnie z oczekiwaniami

    >>> f(1 for x in [1], *[2])
    Success! 
    (generator object <genexpr> at 0x7effe06bdcd0>, 2)
    
  4. To działa, ale nie rozumiem dlaczego. Czy to nie powinno zawieść w taki sam sposób jak 2)
    >>> f(*[2], 1 for x in [1])                                               
    Success!
    (generator object <genexpr> at 0x7effe06bdcd0>, 2)
    
Author: Martijn Pieters, 2015-09-11

1 answers

Oba 3. i 4. powinny być błędami składni we wszystkich wersjach Pythona. jednak znalazłeś błąd, który wpływa na wersje Pythona 2.5 - 3.4, a który został następnie wysłany do śledzenia problemów Pythona . Z powodu błędu, nieparentyfikowane wyrażenie generatora zostało przyjęte jako argument funkcji, jeśli towarzyszyły mu tylko *args i / lub **kwargs. Podczas gdy Python 2.6 + dopuszczał oba przypadki 3. i 4., Python 2.5 dozwolony tylko przypadek 3. - ale obaj byli wobec udokumentowanej gramatyki :

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

Tzn. dokumentacja mówi, że wywołanie funkcji składa się z primary (wyrażenia, które ewaluuje do wywołania), a następnie, w nawiasach, albo {[33] } listy argumentów lub po prostu nieparentowanego wyrażenia generatora; a w obrębie listy argumentów wszystkie wyrażenia generatora muszą znajdować się w nawiasach.


Ten błąd (choć wydaje się, że nie był znany), został naprawiony w Pythonie 3.5 prerelease. W W Pythonie 3.5 nawiasy są zawsze wymagane wokół wyrażenia generatora, chyba że jest to jedyny argument funkcji:

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

Jest to teraz udokumentowane w Co nowego w Pythonie 3.5, Dzięki Detererowi zauważającemu ten błąd.


Analiza błędu

Nastąpiła zmiana w Pythonie 2.6, która pozwoliła na użycie argumentów słów kluczowych Po *args:

Legalne staje się również podawanie argumentów słów kluczowych po a * args argument do wywołania funkcji.

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

Poprzednio był to błąd składni. (Dodany przez Amaury Forgeot d ' Arc; wydanie 3473.)


Gramatyka Pythona 2.6 nie rozróżnia argumentów słów kluczowych, argumentów pozycyjnych lub wyrażeń generatora - wszystkie są typu argument dla parsera.

Zgodnie z regułami Pythona, wyrażenie generatora musi być w nawiasie, jeśli nie jest jedynym argumentem do funkcji. Jest to potwierdzone w 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;
}

Jednak ta funkcja nie bierze pod uwagę w ogóle - w szczególności szuka tylko zwykłych argumentów pozycyjnych i argumentów słów kluczowych.

Dalej w tej samej funkcji generowany jest komunikat o błędzie dla Nie-słowa kluczowego arg po słowie kluczowym 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;
        }
        ...

Ale to znowu odnosi się do argumentów, które są a nie wyrażeniami generatora jako else if} twierdzenie :

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

W ten sposób nieparentowane wyrażenie generatora mogło się poślizgnąć.


Teraz w Pythonie 3.5 Można używać *args wszędzie w wywołaniu funkcji, więc Gramatyka została zmieniona w celu dostosowania do tego:

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

I

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

I for pętla została zmieniona na

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

W ten sposób naprawiamy błąd.

Jednak nieumyślną zmianą jest to, że poprawne spojrzenie konstrukcje

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

I

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

Gdzie nieparentowany generator poprzedza *args lub **kwargs teraz przestał działać.


Aby zlokalizować ten błąd, wypróbowałem różne wersje Pythona. W 2.5 dostaniesz 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])
I to zostało naprawione przed jakąś wersją Pythona 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
W Pythonie 3.5, ale nie działa nie w Pythonie 3.4:
f(*[1], (2 for x in [2]))
I to jest wskazówka. W Python 3.5 *splatting jest uogólniony; można go używać wszędzie w wywołaniu funkcji:
>>> print(*range(5), 42)
0 1 2 3 4 42

Tak więc rzeczywisty błąd (generator pracujący z *star bez nawiasów) był rzeczywiście naprawiony w Pythonie 3.5, A błąd można znaleźć w tym, co zmieniło się między Pythonem 3.4 i 3.5

 73
Author: Antti Haapala,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-15 13:29:55