Генератор как аргумент функции
может ли кто-нибудь объяснить, почему передача генератора в качестве единственного позиционного аргумента функции, похоже, имеет специальные правила?
если у нас есть:
>>> def f(*args):
>>> print "Success!"
>>> print args
это работает, как ожидалось.
>>> f(1, *[2])
Success!
(1, 2)
Это не работает, как ожидалось.
>>> f(*[2], 1)
File "<stdin>", line 1
SyntaxError: only named arguments may follow *expression
это работает, как ожидалось
>>> f(1 for x in [1], *[2])
Success!
(generator object <genexpr> at 0x7effe06bdcd0>, 2)
это работает, но я не понимаю, почему. Разве это не в том же так как 2)
>>> f(*[2], 1 for x in [1])
Success!
(generator object <genexpr> at 0x7effe06bdcd0>, 2)
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