From 2d735bc09876e8216bf6708bb598923e07a2ac4b Mon Sep 17 00:00:00 2001 From: Benjamin Peterson Date: Tue, 19 Aug 2008 20:57:10 +0000 Subject: [PATCH] allow keyword args after *args in a function call --- Doc/reference/expressions.rst | 19 ++++++++++--------- Grammar/Grammar | 4 +++- Lib/test/test_grammar.py | 8 ++++++++ Lib/test/test_keywordonlyarg.py | 2 +- Python/ast.c | 5 +++++ Python/graminit.c | 5 +++-- 6 files changed, 30 insertions(+), 13 deletions(-) diff --git a/Doc/reference/expressions.rst b/Doc/reference/expressions.rst index 1e94d4079e3..945df61f16a 100644 --- a/Doc/reference/expressions.rst +++ b/Doc/reference/expressions.rst @@ -612,11 +612,11 @@ of arguments: call: `primary` "(" [`argument_list` [","] : | `expression` `genexpr_for`] ")" argument_list: `positional_arguments` ["," `keyword_arguments`] - : ["," "*" `expression`] - : ["," "**" `expression`] + : ["," "*" `expression`] ["," `keyword_arguments`] + : ["," "**" `expression`] : | `keyword_arguments` ["," "*" `expression`] - : ["," "**" `expression`] - : | "*" `expression` ["," "**" `expression`] + : ["," `keyword_arguments`] ["," "**" `expression`] + : | "*" `expression` ["," `keyword_arguments`] ["," "**" `expression`] : | "**" `expression` positional_arguments: `expression` ("," `expression`)* keyword_arguments: `keyword_item` ("," `keyword_item`)* @@ -674,12 +674,13 @@ there were no excess keyword arguments. If the syntax ``*expression`` appears in the function call, ``expression`` must evaluate to a sequence. Elements from this sequence are treated as if they were -additional positional arguments; if there are positional arguments *x1*,...,*xN* -, and ``expression`` evaluates to a sequence *y1*,...,*yM*, this is equivalent -to a call with M+N positional arguments *x1*,...,*xN*,*y1*,...,*yM*. +additional positional arguments; if there are positional arguments *x1*,..., +*xN*, and ``expression`` evaluates to a sequence *y1*, ..., *yM*, this is +equivalent to a call with M+N positional arguments *x1*, ..., *xN*, *y1*, ..., +*yM*. -A consequence of this is that although the ``*expression`` syntax appears -*after* any keyword arguments, it is processed *before* the keyword arguments +A consequence of this is that although the ``*expression`` syntax may appear +*after* some keyword arguments, it is processed *before* the keyword arguments (and the ``**expression`` argument, if any -- see below). So:: >>> def f(a, b): diff --git a/Grammar/Grammar b/Grammar/Grammar index 6111fae79b8..64816d9b2ce 100644 --- a/Grammar/Grammar +++ b/Grammar/Grammar @@ -113,7 +113,9 @@ dictorsetmaker: ( (test ':' test (comp_for | (',' test ':' test)* [','])) | classdef: 'class' NAME ['(' [arglist] ')'] ':' suite -arglist: (argument ',')* (argument [',']| '*' test [',' '**' test] | '**' test) +arglist: (argument ',')* (argument [','] + |'*' test (',' argument)* [',' '**' test] + |'**' test) argument: test [comp_for] | test '=' test # Really [keyword '='] test comp_iter: comp_for | comp_if diff --git a/Lib/test/test_grammar.py b/Lib/test/test_grammar.py index acfe1f15733..9a8846307ec 100644 --- a/Lib/test/test_grammar.py +++ b/Lib/test/test_grammar.py @@ -284,6 +284,14 @@ class GrammarTests(unittest.TestCase): pos2key2dict(1,2,k2=100,tokwarg1=100,tokwarg2=200) pos2key2dict(1,2,tokwarg1=100,tokwarg2=200, k2=100) + # keyword arguments after *arglist + def f(*args, **kwargs): + return args, kwargs + self.assertEquals(f(1, x=2, *[3, 4], y=5), ((1, 3, 4), + {'x':2, 'y':5})) + self.assertRaises(SyntaxError, eval, "f(1, *(2,3), 4)") + self.assertRaises(SyntaxError, eval, "f(1, x=2, *(3,4), x=5)") + # argument annotation tests def f(x) -> list: pass self.assertEquals(f.__annotations__, {'return': list}) diff --git a/Lib/test/test_keywordonlyarg.py b/Lib/test/test_keywordonlyarg.py index aa7d3db8946..b771a6c0221 100644 --- a/Lib/test/test_keywordonlyarg.py +++ b/Lib/test/test_keywordonlyarg.py @@ -75,7 +75,7 @@ class KeywordOnlyArgTestCase(unittest.TestCase): def testSyntaxErrorForFunctionCall(self): self.assertRaisesSyntaxError("f(p, k=1, p2)") - self.assertRaisesSyntaxError("f(p, *(1,2), k1=100)") + self.assertRaisesSyntaxError("f(p, k1=50, *(1,2), k1=100)") def testRaiseErrorFuncallWithUnexpectedKeywordArgument(self): self.assertRaises(TypeError, keywordonly_sum, ()) diff --git a/Python/ast.c b/Python/ast.c index 46bfd4e8034..6d2fa09b17d 100644 --- a/Python/ast.c +++ b/Python/ast.c @@ -1961,6 +1961,11 @@ ast_for_call(struct compiling *c, const node *n, expr_ty func) "non-keyword arg after keyword arg"); return NULL; } + if (vararg) { + ast_error(CHILD(ch, 0), + "only named arguments may follow *expression"); + return NULL; + } e = ast_for_expr(c, CHILD(ch, 0)); if (!e) return NULL; diff --git a/Python/graminit.c b/Python/graminit.c index e26bcc5e261..6f3787ac57a 100644 --- a/Python/graminit.c +++ b/Python/graminit.c @@ -1598,7 +1598,8 @@ static arc arcs_73_5[2] = { static arc arcs_73_6[1] = { {0, 6}, }; -static arc arcs_73_7[1] = { +static arc arcs_73_7[2] = { + {161, 5}, {32, 3}, }; static state states_73[8] = { @@ -1609,7 +1610,7 @@ static state states_73[8] = { {4, arcs_73_4}, {2, arcs_73_5}, {1, arcs_73_6}, - {1, arcs_73_7}, + {2, arcs_73_7}, }; static arc arcs_74_0[1] = { {24, 1},