Several Ways to Pass Function Arguments in Python

Several Ways to Pass Function Arguments in Python

In Python, passing function arguments is essentially binding objects to the function's parameter names. You can also think of it as passing through "assignment." This rule answers the general principle of argument passing, but there are still two details to explain further: how function parameters are defined, and how arguments are resolved when a function is called.

There are four common forms of function parameter definitions:

  1. F(arg1, arg2, ...)
  2. F(arg2=<value>, arg3=<value>, ...)
  3. F(*args)
  4. F(**kwargs)

1. Regular Positional Parameters

The first form is the most traditional one. A function can define several parameters. The formal parameters are placed inside the parentheses after the function name, with each parameter separated by a comma.

For a function defined this way, the call must provide the same number of actual arguments: neither more nor fewer. The order must also match. In other words, formal parameters and actual arguments correspond one to one: parameter1 = argument1, parameter2 = argument2, and so on.

For example:

def add_on(x, y):
    return x + y

This function can be called like this:

add_on(1, 2)

This means the parameter x takes the value 1, and the parameter y takes the value 2. The following two calls are both wrong:

add_on(1, 2, 3)  # too many arguments
add_on(1)        # too few arguments

2. Default Parameters and Keyword Arguments

The second form assigns default values to parameters when defining the function. When calling the function, if no argument is passed for the corresponding parameter, that parameter uses its default value.

For example:

def add_on(x=3, y=5):
    return x + y

Calling:

add_on(6, 5)

means x takes the value 6, and y takes the value 5.

Calling:

add_on(7)

means x takes the value 7, and y uses its default value 5.

This raises a question: what if you want x to use its default value and only assign a value to y? This is where keyword arguments in Python function calls are used:

add_on(y=6)

This means x uses its default value 3, and y takes the value 6.

Keyword arguments can specify parameter names directly, so they do not need to follow the order in which the parameters were defined. For example:

add_on(y=4, x=6)

This form of assigning values directly by parameter name also applies to functions that are defined only with regular positional parameters.

One thing to note is a common pitfall with default parameters: do not use mutable objects as default values, such as lists or dictionaries. Default values are created only once, when the function is defined, and later calls reuse the same object.

3. Variable Positional Arguments *args

In the first two forms, the number of defined parameters is fixed. For example, if a function defines five parameters, at most five actual arguments can be passed when calling it.

In real programming, however, it is not always possible to know in advance how many arguments a function will receive. The third form is used for this situation. It uses an asterisk plus a parameter name, such as *args. This function can receive any number of positional arguments: zero, or N arguments. The passed arguments are collected into a tuple inside the function.

For example:

def add_on(*args):
    total = 0
    for value in args:
        total += value
    return total

All of the following calls are valid:

add_on()
add_on(2)
add_on(3, 4, 5, 6)

Inside the function, args corresponds respectively to:

()
(2,)
(3, 4, 5, 6)

4. Variable Keyword Arguments **kwargs

Similar to the third form, putting two asterisks before a parameter name means it receives any number of keyword arguments. These arguments are collected into a dictionary inside the function.

When calling this kind of function, arguments must be passed in the form key=value. For example:

def add_on(**kwargs):
    total = 0
    if len(kwargs) == 0:
        return 0
    for value in kwargs.values():
        total += value
    return total

It can be called like this:

add_on()
add_on(x=4, y=5, k=6)

For add_on(x=4, y=5, k=6), the kwargs obtained inside the function is:

{'x': 4, 'y': 5, 'k': 6}

If you are using Python 2, dictionary traversal can also be written as kwargs.itervalues(); in Python 3, use kwargs.values().

Combining Parameter Forms

The four parameter definition forms above can be combined to create more complex and flexible function definitions. When defining a function, you generally follow this order:

  1. Regular positional parameters, such as arg
  2. Default parameters, such as arg=<value>
  3. Variable positional parameters, such as *args
  4. Variable keyword parameters, such as **kwargs

That is:

def func(arg, default_arg=value, *args, **kwargs):
    pass

During a function call, the general process of assigning values to parameters is:

  1. First, assign regular positional arguments to the corresponding parameters in order.
  2. Then assign keyword arguments in the form arg=<value> to the corresponding parameters.
  3. Extra positional arguments are collected into a tuple and assigned to the parameter with one asterisk.
  4. Extra keyword arguments are collected into a dictionary and assigned to the parameter with two asterisks.

This may sound complicated, but it is quite intuitive in practice. Consider the following example:

def test(x, y=5, *a, **b):
    print(x, y, a, b)

The results of different call forms are as follows:

test(1)                  # 1 5 () {}
test(1, 2)               # 1 2 () {}
test(1, 2, 3)            # 1 2 (3,) {}
test(1, 2, 3, 4)         # 1 2 (3, 4) {}

test(x=1)                # 1 5 () {}
test(x=1, y=1)           # 1 1 () {}
test(x=1, y=1, a=1)      # 1 1 () {'a': 1}
test(x=1, y=1, a=1, b=1) # 1 1 () {'a': 1, 'b': 1}

test(1, y=1)             # 1 1 () {}
test(1, 2, y=1)          # TypeError: test() got multiple values for argument 'y'
test(1, 2, 3, 4, a=1)   # 1 2 (3, 4) {'a': 1}

Here, test(1, 2, y=1) raises an error because the positional argument 2 has already been assigned to y, and then the keyword argument y=1 assigns a value to y again. Therefore, Python reports that y received multiple values.

To summarize: regular positional parameters are suitable for functions with a fixed number of arguments; default parameters are suitable for setting default behavior for common values; args is suitable for receiving an indefinite number of positional arguments; and kwargs is suitable for receiving an indefinite number of keyword arguments. When writing functions in practice, keep the parameter design as clear as possible, and use args and **kwargs only when flexible extension is truly needed.

Leave a Reply