Generator jako argument funkcji

81

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

Jeśli mamy:

def f(*args):
    print "Success!"
    print args
  1. Działa to 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. Działa to 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 nie powinno zawieść w taki sam sposób, jak 2)

    >>> f(*[2], 1 for x in [1])
    Success!
    (generator object <genexpr> at 0x7effe06bdcd0>, 2)
    
DeTeReR
źródło
1
Nie jest to dokładny duplikat, ale dość podobny: stackoverflow.com/questions/12720450/… . TL; DR wydaje się, że to szczegół implementacji - po prostu tak działa.
J0HN,
2
Uwaga: przypadek 2 powinien działać w Pythonie 3.5+ (ze względu na PEP 448 )
Bakuriu
1
Python 3.5 jest niedostępny i teraz informuje, że przypadek 3 (właściwie również przypadek 4) został naprawiony. Co nowego w Pythonie 3.5
Antti Haapala

Odpowiedzi:

76

Zarówno 3., jak i 4. powinny być błędami składniowymi we wszystkich wersjach Pythona. Jednak znalazłeś błąd, który dotyczy wersji Pythona 2.5 - 3.4 i który został następnie wysłany do modułu śledzenia problemów Pythona . Z powodu błędu nieprzedstawione wyrażenie generatora było akceptowane jako argument funkcji, jeśli towarzyszyło mu tylko *argsi / lub **kwargs. Podczas gdy Python 2.6+ dopuszczał oba przypadki 3. i 4., Python 2.5 dopuszczał tylko przypadek 3. - ale oba były sprzeczne z udokumentowaną gramatyką :

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

czyli dokumentacja mówi Wywołanie funkcji składa się z primary(wyrażenie, którego wynikiem jest wymagalne), a następnie, w nawiasie, albo na liście argumentów lub tylko unparenthesized ekspresji generatora; a na liście argumentów wszystkie wyrażenia generatora muszą znajdować się w nawiasach.


Ten błąd (choć wydaje się, że nie był znany), został naprawiony w wydaniach wstępnych Pythona 3.5. 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 DeTeReR, który wykrył ten błąd.


Analiza błędu

Wprowadzono zmianę w Pythonie 2.6, która pozwoliła na użycie argumentów słów kluczowych po *args :

Dozwolone jest również podawanie argumentów słów kluczowych po argumencie * args 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}

Wcześniej byłby to błąd składniowy. (Nadesłane przez Amaury Forgeot d'Arc; wydanie 3473.)


Jednak gramatyka Pythona 2.6 nie czyni żadnego rozróżnienia między argumentami słów kluczowych, argumentami pozycyjnymi lub zwykłymi wyrażeniami generatora - wszystkie są typuargument samych dla parsera.

Zgodnie z regułami Pythona, wyrażenie generatora musi być umieszczone w nawiasach, jeśli nie jest jedynym argumentem 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 uwzględnia*args - w szczególności szuka tylko zwykłych argumentów pozycyjnych i argumentów słów kluczowych.

W dalszej części tej samej funkcji pojawia się komunikat o błędzie generowany dla argumentu innego niż słowo kluczowe po argumencie kluczowym :

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 nie są wyrażeniami generatora bez rodzicielstwa, czego dowodem jest else ifstwierdzenie :

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 zezwolono na przejście przez nieprzedstawione wyrażenie generatora.


Teraz w Pythonie 3.5 można używać *argsdowolnego miejsca w wywołaniu funkcji, więc gramatyka została zmieniona, aby dostosować się do tego:

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

i

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

a forpę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 poprawnie wyglądające konstrukcje

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

i

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

w przypadku, gdy generator, którego nie ma wcześniej, poprzedza *argslub **kwargsprzestał działać.


Aby zlokalizować ten błąd, próbowałem różnych wersji 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 zostało to naprawione przed premierą 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

Jednak wyrażenie generatora w nawiasach działa w Pythonie 3.5, ale nie działa w Pythonie 3.4:

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

I to jest wskazówka. W Pythonie 3.5 *splattingjest to uogólnione; możesz go używać w dowolnym miejscu wywołania funkcji:

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

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

Antti Haapala
źródło
1
Nie jest to poprawione w 3.5 - po prostu umieść parens wokół generatora i zachowanie jest takie samo.
viraptor
1
@viraptor to dobra uwaga, w 3.4 wyrażenie w nawiasach podaje błąd
Antti Haapala
co? Działa w dniu 3.4.3: f(*[1], 1 for x in [1])=>(<generator object <genexpr> at 0x7fa56c889288>, 1)
viraptor,
@viraptor f(*[1], (1 for x in [1]))to błąd składni w Pythonie 3.4. Działa w Pythonie 3.5.
Antti Haapala
Pozłacałbym tę odpowiedź, gdybym mógł, dzięki za dołączenie odpowiedniego źródła C!
Nick Sweeting