Может ли кто-нибудь объяснить, почему передача генератора в качестве единственного позиционного аргумента функции, похоже, имеет особые правила?
Если мы имеем:
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)
Ответы:
И 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
:Однако грамматика 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.источник
f(*[1], 1 for x in [1])
=>(<generator object <genexpr> at 0x7fa56c889288>, 1)
f(*[1], (1 for x in [1]))
- синтаксическая ошибка на Python 3.4. Это действительно в Python 3.5.