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':'CA','city':'Vancouver','state':'BC'} 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
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 is 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.