Генератор как аргумент функции

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
Антти Хаапала

Ответы:

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}

Раньше это было бы синтаксической ошибкой. (Предоставлено Амори Форжеот д'Арк; выпуск 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 - она ​​специально ищет только обычные позиционные аргументы и аргументы ключевого слова.

Далее в той же функции появляется сообщение об ошибке для аргумента без ключевого слова после ключевого слова 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;
        }
        ...

Но это снова относится к аргументам, которые не являются выражениями генератора без скобок, о чем свидетельствует 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 - просто поместите скобки вокруг генератора, и поведение останется таким же.
viraptor
1
@viraptor хороший момент, в 3.4 выражение в скобках дает ошибку
Антти Хаапала
а? Запуск на 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.
Антти Хаапала,
Если бы я мог, я бы получил этот ответ, спасибо за включение соответствующего источника на C!
Ник Свитинг