Skip to content

Commit 2d735bc

Browse files
committed
allow keyword args after *args in a function call
1 parent de0de88 commit 2d735bc

6 files changed

Lines changed: 30 additions & 13 deletions

File tree

Doc/reference/expressions.rst

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -612,11 +612,11 @@ of arguments:
612612
call: `primary` "(" [`argument_list` [","]
613613
: | `expression` `genexpr_for`] ")"
614614
argument_list: `positional_arguments` ["," `keyword_arguments`]
615-
: ["," "*" `expression`]
616-
: ["," "**" `expression`]
615+
: ["," "*" `expression`] ["," `keyword_arguments`]
616+
: ["," "**" `expression`]
617617
: | `keyword_arguments` ["," "*" `expression`]
618-
: ["," "**" `expression`]
619-
: | "*" `expression` ["," "**" `expression`]
618+
: ["," `keyword_arguments`] ["," "**" `expression`]
619+
: | "*" `expression` ["," `keyword_arguments`] ["," "**" `expression`]
620620
: | "**" `expression`
621621
positional_arguments: `expression` ("," `expression`)*
622622
keyword_arguments: `keyword_item` ("," `keyword_item`)*
@@ -674,12 +674,13 @@ there were no excess keyword arguments.
674674

675675
If the syntax ``*expression`` appears in the function call, ``expression`` must
676676
evaluate to a sequence. Elements from this sequence are treated as if they were
677-
additional positional arguments; if there are positional arguments *x1*,...,*xN*
678-
, and ``expression`` evaluates to a sequence *y1*,...,*yM*, this is equivalent
679-
to a call with M+N positional arguments *x1*,...,*xN*,*y1*,...,*yM*.
677+
additional positional arguments; if there are positional arguments *x1*,...,
678+
*xN*, and ``expression`` evaluates to a sequence *y1*, ..., *yM*, this is
679+
equivalent to a call with M+N positional arguments *x1*, ..., *xN*, *y1*, ...,
680+
*yM*.
680681

681-
A consequence of this is that although the ``*expression`` syntax appears
682-
*after* any keyword arguments, it is processed *before* the keyword arguments
682+
A consequence of this is that although the ``*expression`` syntax may appear
683+
*after* some keyword arguments, it is processed *before* the keyword arguments
683684
(and the ``**expression`` argument, if any -- see below). So::
684685

685686
>>> def f(a, b):

Grammar/Grammar

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,9 @@ dictorsetmaker: ( (test ':' test (comp_for | (',' test ':' test)* [','])) |
113113

114114
classdef: 'class' NAME ['(' [arglist] ')'] ':' suite
115115

116-
arglist: (argument ',')* (argument [',']| '*' test [',' '**' test] | '**' test)
116+
arglist: (argument ',')* (argument [',']
117+
|'*' test (',' argument)* [',' '**' test]
118+
|'**' test)
117119
argument: test [comp_for] | test '=' test # Really [keyword '='] test
118120

119121
comp_iter: comp_for | comp_if

Lib/test/test_grammar.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,14 @@ def pos2key2dict(p1, p2, *, k1=100, k2, **kwarg): return p1,p2,k1,k2,kwarg
284284
pos2key2dict(1,2,k2=100,tokwarg1=100,tokwarg2=200)
285285
pos2key2dict(1,2,tokwarg1=100,tokwarg2=200, k2=100)
286286

287+
# keyword arguments after *arglist
288+
def f(*args, **kwargs):
289+
return args, kwargs
290+
self.assertEquals(f(1, x=2, *[3, 4], y=5), ((1, 3, 4),
291+
{'x':2, 'y':5}))
292+
self.assertRaises(SyntaxError, eval, "f(1, *(2,3), 4)")
293+
self.assertRaises(SyntaxError, eval, "f(1, x=2, *(3,4), x=5)")
294+
287295
# argument annotation tests
288296
def f(x) -> list: pass
289297
self.assertEquals(f.__annotations__, {'return': list})

Lib/test/test_keywordonlyarg.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def testSyntaxForManyArguments(self):
7575

7676
def testSyntaxErrorForFunctionCall(self):
7777
self.assertRaisesSyntaxError("f(p, k=1, p2)")
78-
self.assertRaisesSyntaxError("f(p, *(1,2), k1=100)")
78+
self.assertRaisesSyntaxError("f(p, k1=50, *(1,2), k1=100)")
7979

8080
def testRaiseErrorFuncallWithUnexpectedKeywordArgument(self):
8181
self.assertRaises(TypeError, keywordonly_sum, ())

Python/ast.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1961,6 +1961,11 @@ ast_for_call(struct compiling *c, const node *n, expr_ty func)
19611961
"non-keyword arg after keyword arg");
19621962
return NULL;
19631963
}
1964+
if (vararg) {
1965+
ast_error(CHILD(ch, 0),
1966+
"only named arguments may follow *expression");
1967+
return NULL;
1968+
}
19641969
e = ast_for_expr(c, CHILD(ch, 0));
19651970
if (!e)
19661971
return NULL;

Python/graminit.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1598,7 +1598,8 @@ static arc arcs_73_5[2] = {
15981598
static arc arcs_73_6[1] = {
15991599
{0, 6},
16001600
};
1601-
static arc arcs_73_7[1] = {
1601+
static arc arcs_73_7[2] = {
1602+
{161, 5},
16021603
{32, 3},
16031604
};
16041605
static state states_73[8] = {
@@ -1609,7 +1610,7 @@ static state states_73[8] = {
16091610
{4, arcs_73_4},
16101611
{2, arcs_73_5},
16111612
{1, arcs_73_6},
1612-
{1, arcs_73_7},
1613+
{2, arcs_73_7},
16131614
};
16141615
static arc arcs_74_0[1] = {
16151616
{24, 1},

0 commit comments

Comments
 (0)