Генератор як аргумент функції


81

Хто-небудь може пояснити, чому передача генератора як єдиного позиційного аргументу функції, схоже, має спеціальні правила?

Якщо ми маємо:

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)
    

1
Не точний дублікат, але цілком схожий: stackoverflow.com/questions/12720450/… . TL; DR здається, що це деталь реалізації - це просто так працює.
J0HN

2
Примітка: випадок 2 повинен працювати в python 3.5+ (через PEP 448 )
Бакуріу

1
Python 3.5 вийшов, і тепер він повідомляє, що випадок 3 (насправді також випадок 4) виправлений. Що нового в Python 3.5
Antti Haapala

Відповіді:


76

І 3., і 4. повинні бути синтаксичними помилками у всіх версіях Python. Однак ви виявили помилку, яка впливає на версії Python 2.5 - 3.4 і яка згодом була опублікована в засобі відстеження випусків Python . Через помилку неприйнятий вираз генератора був прийнятий як аргумент функції, якщо він супроводжувався лише *argsта / або **kwargs. У той час як Python 2.6+ допускав обидва випадки 3. та 4., Python 2.5 допускав лише регістр 3. - проте обидва вони були проти документованої граматики :

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

тобто в документації сказано, що виклик функції складається з primary(вираз, що перетворюється на викличний), за яким у дужках йде або список аргументів, або просто непрозорий вираз генератора; а в списку аргументів усі вирази генератора повинні бути в дужках.


Ця помилка (хоча, здається, вона не була відома), була виправлена ​​в попередніх випусках Python 3.5. У 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 for i in [42], *a)
  File "<stdin>", line 1
SyntaxError: Generator expression must be parenthesized if not sole argument

Зараз це задокументовано в Що нового в Python 3.5 , завдяки 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; випуск 3473.)


Однак граматика Python 2.6 не робить жодної різниці між аргументами ключових слів, позиційними аргументами або оголеними виразами генератора - всі вони є типовими argumentдля синтаксичного аналізатора.

Відповідно до правил Python, вираз генератора повинен бути в дужках, якщо це не єдиний аргумент функції. Це підтверджено в 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;
}

Однак ця функція взагалі не розглядає *args- вона спеціально шукає лише звичайні позиційні аргументи та аргументи ключових слів.

Далі в тій самій функції з’являється повідомлення про помилку для аргументу, що не є ключовим словом, після аргументу ключового слова :

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твердження :

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

Таким чином, неприйнятому виразу генератора було дозволено проскочити прохід.


Тепер у 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++;
    }
}

Таким чином виправлено помилку.

Однак ненавмисна зміна полягає в тому, що дійсно виглядають конструкції

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

і

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

де невідомий генератор передує *argsабо **kwargsперестав працювати.


Щоб знайти цю помилку, я спробував різні версії 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

Однак, вираз генератора в дужках, він працює в 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

Отже, фактична помилка (генератор, що працює *starбез дужок) справді була виправлена ​​в Python 3.5, і помилку можна було знайти в тому, що змінилося між Python 3.4 та 3.5


1
Це не зафіксовано в 3.5 - просто розмістіть панелі навколо генератора, і поведінка буде однаковою.
віраптор

1
@viraptor хороший момент, у 3.4 вираз у дужках дає помилку
Antti Haapala

так? Працює 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]))- це синтаксична помилка на Python 3.4. Це дійсно в Python 3.5.
Antti Haapala,

Я б позолотив цю відповідь, якби міг, дякую, що включив відповідне джерело на С!
Нік Світінг
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.