8

The usage of *args and **kwargs in python is clear to me and there are many questions out there in SO (eg Use of *args and **kwargs and What does ** (double star/asterisk) and * (star/asterisk) do for parameters?).

But one thing I would like to understand is: why is it not possible to simultaneously define mandatory positional args, mandatory kwarg arguments and eventually still allow catching other args and kwargs as in cant_do_that below?

def one_kwarg_is_mandatory(*, b, **kwargs):
    print(b)
    for key, value in kwargs.items():
        print(key, value)    
        
def one_pos_arg_and_one_kwarg_are_mandatory(a, *, b, **kwargs):
    print(a, b)
    for key, value in kwargs.items():
        print(key, value)
      
# I wanted a mandatory arg (a) and possibly parse other args (*args), 
# then a mandatory kwarg (b) and eventually other kwargs (**kwargs)
def cant_do_that(a, *args, *, b, **kwargs):
    print(a, b)
    print(args)
    for key, value in kwargs.items():
        print(key, value)

# not really interested on this because "b" must be a kwarg and hiding 
# it under **kwargs would not be explicit enough for my customer (sometimes myself ;))
def could_do_this_but(a, b, *args, **kwargs):
    print(a, b)
    print(args)
    print(kwargs)

Yes one could get rid of b in the could_do_this_but function's signature, perform (for instance) a kwargs.get("b", None) at the top of the function and raise some appropriate error if found None... but having "b" directly on the function signature would allow faster and more explicit code development employing the function down the road.

wjandrea
  • 28,235
  • 9
  • 60
  • 81
deponovo
  • 1,114
  • 7
  • 23

1 Answers1

8

The correct syntax is def cant_do_that(a, *args, b, **kwargs):. Note that * is used only once, both to mark the end of positional arguments and to set the name for variadic positional arguments.


The * in a function definition is syntactically unique at the separation between positional-or-keyword and keyword-only arguments:

parameter_list_starargs   ::=  "*" [parameter] ("," defparameter)* ["," ["**" parameter [","]]]
                               | "**" parameter [","]

In short, the grammar "*" [parameter] means * and *args are syntactically the same thing – a literal * and optional name – which may occur only once. Use a bare * to start keyword-only arguments without taking variadic positional arguments, and use a named *args to start keyword-only arguments with taking variadic positional arguments.

If the form “*identifier” is present, it is initialized to a tuple receiving any excess positional parameters, defaulting to the empty tuple. [...] Parameters after “*” or “*identifier” are keyword-only parameters and may only be passed used keyword arguments.

MisterMiyagi
  • 44,374
  • 10
  • 104
  • 119