Search code examples
pythonsyntaxpython-internals

Why is a trailing comma a SyntaxError in an argument list that uses *args syntax?


Why can't you use a trailing comma with *args in Python? In other words, this works

>>> f(1, 2, b=4,)

But this does not

>>> f(*(1, 2), b=4,)
  File "<stdin>", line 1
    f(*(1, 2), b=4,)
                   ^
SyntaxError: invalid syntax

This is the case with both Python 2 and Python 3.


Solution

  • Let's look at the language specification:

    call                 ::=  primary "(" [argument_list [","]
                              | expression genexpr_for] ")"
    argument_list        ::=  positional_arguments ["," keyword_arguments]
                                ["," "*" expression] ["," keyword_arguments]
                                ["," "**" expression]
                              | keyword_arguments ["," "*" expression]
                                ["," "**" expression]
                              | "*" expression ["," "*" expression] ["," "**" expression]
                              | "**" expression
    positional_arguments ::=  expression ("," expression)*
    keyword_arguments    ::=  keyword_item ("," keyword_item)*
    keyword_item         ::=  identifier "=" expression
    

    Let's sift down to the parts we care about:

    call                 ::=  primary "(" [argument_list [","]] ")"
    argument_list        ::=  positional_arguments ["," keyword_arguments]
                                ["," "*" expression] ["," keyword_arguments]
                                ["," "**" expression]
    positional_arguments ::=  expression ("," expression)*
    keyword_arguments    ::=  keyword_item ("," keyword_item)*
    keyword_item         ::=  identifier "=" expression
    

    So, it looks like after any arguments to a function call, we're allowed an extra ,. So this looks like a bug in the cpython implementation.

    Something like: f(1, *(2,3,4), ) should work according to this grammar, but doesn't in CPython.


    In an earlier answer, Eric linked to the CPython grammar specification, which includes the CPython implementation of the above grammar. Here it is below:

    arglist: (argument ',')* ( argument [',']
                             | '*' test (',' argument)* [',' '**' test] 
                             | '**' test
                             )
    

    Note, that this grammar is not the same as the one proposed by the language specification. I'd consider this an implementation bug.


    Note that there are additional issues with the CPython implementation. This should also be supported: f(*(1,2,3), *(4,5,6))

    Oddly though, the specification does not allow f(*(1,2,3), *(4,5,6), *(7,8,9))

    As I look at this more, I think this part of the specification needs some fixing. This is allowed: f(x=1, *(2,3)), but this isn't: f(x=1, 2, 3).


    And to perhaps be helpful to the original question, in CPython, you can have a trailing comma if you don't use the *args or the **kwargs feature. I agree that this is lame.