Methods arguments: Default, optional, *args & **kwargs

Methods are one of the most common things you'll create in Python. Declared with the syntax def <method_name>(arguments) and called with the syntax result = <method_name>(input), what can be tricky to understand about Python methods is their argument/input syntax.

The obvious way you define and call methods is to have the same number of arguments on both sides, but this is far from most real world cases where data can be missing or the number of arguments can change based on the circumstances. Listing A-9 illustrates a basic method definition with multiple calls using different syntax.

Listing A-9. Python method definition and call syntax with * and **

def address(city,state,country):
    print(city)
    print(state)
    print(country)

address('San Diego','CA','US')
address('Vancouver','BC','CA')

us_address = ('San Diego','CA','US')
address(*us_address)

canada_address = {'country':'US','city':'San Diego','state':'CA'}
address(**canada_address)

The address method in listing A-9 represents the most obvious syntax with three input parameters. Next, you can see two calls are made to the method also with the also obvious syntax: address('San Diego','CA','US') and address('Vancouver','BC','CA'). Following these calls, are another two calls made with the not so obvious syntax address(*us_address) and address(**canada_address).

A variable preceded with * is called a positional argument and tells Python to unpack the tuple assigned to it, which in turn passes each value as an individual argument. A variable preceded with ** is called a keyword argument and tells Python to unpack the dictionary assigned to it, which in turn passes each value as an individual argument.

As you can confirm in listing A-9, the us_address variable is in fact a three item tuple which is then used as the input argument in address(*us_address). And the canada_address variable is in fact a three item dictionary which is then used as the input argument in address(**canada_address).

An interesting behavior of the values in ** is they don't have to follow a strict order (i.e. as expected by the method). Because these values are classified by keyword in a dictionary, Python can map each keyword according to the expected method argument order. This is unlike the values in * which need to be in the same order expected by a method, which is why they're called positional arguments because position matters.

Another important Python method syntax is making an argument optional. In listing A-10 the address_with_default method uses a default argument value for country, which in turn makes it optional.

Listing A-10. Python method optional arguments

def address_with_default(city,state,country='US'):
    print(city)
    print(state)
    print(country)

address_with_default('San Diego','CA')

address_with_default('Vancouver','BC','CA')

address_with_default(**{'state':'CA','city':'San Diego'})

The first call in listing A-10 address_with_default('San Diego','CA') only provides two input arguments and the call still works, even though the address_with_default method definition declares three input arguments. This is because the missing country argument takes the default method value US.

The second call in listing A-10 address_with_default('Vancouver','BC','CA') provides all three input arguments with the third value effectively overriding the default method value US for CA. Finally, the third call in listing A-10 address_with_default(**{'state':'CA','city':'San Diego'}) uses the ** syntax to unpack an inline dictionary with two values, the missing argument country takes the default method value US.

In addition to using the positional * syntax for calling methods, it's also possible to use this same syntax to define method arguments, as illustrated in listing A-11.

Listing A-11. Python method positional argument

def vowels(*args):
    print("*args is %s" % type(args))
    print("Arguments %s " % ', '.join(args))
    
vowels('a')
vowels('a','e')
vowels('a','e','i')
vowels('a','e','i','o')
vowels('a','e','i','o','u')

Notice the method in listing A-11 def vowels(*args) uses the * syntax to define the method argument. The * character has the same meaning described earlier, which is to unpack the values in a tuple. In this case, by using it in a method argument, it gives the method the flexibility to accept any number of arguments. You can confirm this flexibility by seeing the various calls to the vowels() method in listing A-11 which take from one to five arguments.

Because you can't directly reference input arguments with a positional argument -- unless you manually split them -- listing A-12 show another method which first defines a standard input variable and then declares a positional argument.

Listing A-12. Python method with standard and positional argument

def address_with_zipcode(zipcode,*args):
    print(zipcode)
    print("*args is %s" % type(args))
    print("Arguments %s " % ', '.join(args))

address_with_zipcode(92101,'100 Park Boulevard','San Diego','CA','US')

address_with_zipcode('V6B 4Y8','777 Pacific Boulevard','Vancouver','BC','CA')

As you can confirm with the various calls made to address_with_zipcode() in listing A-12, the first value is assigned to the first input variable and the rest of the values are assigned to the positional argument. Listing A-13 illustrates yet another method syntax which uses a keyword argument in the method definition.

Listing A-13 Python method with keyword argument

def address_catcher(**kwargs):
    print("**kwargs is %s" % type(kwargs))
    print("Keyword arguments %s " % ', '.join(['%s = %s' % (k,v) for k,v in kwargs.items()]))

address_catcher(zipcode=92101,street='100 Park Boulevard',city='San Diego',
                                                             state='CA',country='US')

address_catcher(postalcode='V6B 4Y8',street='777 Pacific Boulevard',city='Vancouver',
                                                          province='BC',country='CA')

Notice the method in listing A-13 uses the ** syntax to define the method argument. The ** character has the same meaning described earlier, which it to unpack the values in a dictionary. In this case, by using it in a method argument, it gives the method the flexibility to accept any number of keyword arguments. You can confirm this flexibility by seeing the various calls to address_catcher() in listing A-13 which use different keys, one call uses zipcode and state, while the other uses postalcode and province.

Finally, listing A-14 illustrates a method that uses a standard input variable, a positional argument and a keyword argument.

Listing A-14 Python method with standard, positional and keyword argument

def address_full(country,*args,**kwargs):
    print(country)
    print("*args is %s" % type(args))
    print("Arguments %s " % ', '.join(args))
    print("**kwargs is %s" % type(kwargs))
    print("Keyword arguments %s " % ', '.join(['%s = %s' % (k,v) for k,v in kwargs.items()]))
address_full('US','100 Park Boulevard','San Diego',state='CA',zipcode=92101)
address_full('CA','777 Pacific Boulevard','Vancouver',province='BC',postalcode='V6B 4Y8')

If you look over the sample calls to address_full() method in listing A-14, you'll see this process works by assigning input values as the method slots requires. The first input value is always assigned to the standard input variable country, the next input values up to the first keyword=value are assigned to the positional argument *args, and all the input values with a keyword=value syntax are assigned to the keyword argument **kwargs. Be aware the input sequence when you use all three types of input types must always follow this order to avoid ambiguity.

Note The * and ** characters are what really matters in Python methods. Syntax wise you're more likely to encounter the references *args and **kwargs, but what really matters are the * and ** characters. To Python, you can equally declare *foo and **bar, however, the names *args and **kwargs are so prevalent you're more likely to encounter these than custom names.