І 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