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



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



если у нас есть:



>>> 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)


1072   1  

1 ответ:

обе 3. и 4. должны синтаксические ошибки на всех версиях Python. однако вы нашли ошибку, которая влияет на версии Python 2.5-3.4, и которая была впоследствии опубликовано в Python issue tracker. Из-за ошибки непарентезированное выражение генератора было принято в качестве аргумента функции, если оно сопровождалось только *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, благодаря Детергенту, обнаруживающему эту ошибку.


анализ ошибок

в Python 2.6 было внесено изменение, которое разрешено использование аргументов ключевых слов после*args:

также стало законным предоставлять аргументы ключевых слов после A * 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}

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

далее вниз в той же функции, появляется сообщение об ошибке для не-ключевое слово arg после ключевого слова 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 the *splatting обобщен; вы можете использовать его в любом месте в функция звоните:

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

так что фактическая ошибка (генератор работает с *star без скобок) был действительно исправлена в Python 3.5, и ошибка может быть найдена в том, что изменилось между Python 3.4 и 3.5

Comments

    Ничего не найдено.