Home
learn

# Function arguments

In this article we will talk about function parameters and function arguments in detail. We will learn:

• The difference between arguments and parameters
• Positional and keyword arguments
• Default arguments
• Variable-length arguments (`*args` and `**kwargs`)
• Container unpacking into function arguments
• Local vs. global arguments
• Parameter passing (by value or by reference?)

## Arguments and parameters

• Parameters are the variables that are defined or used inside parentheses while defining a function
• Arguments are the value passed for these parameters while calling a function
``````def print_name(name): # name is the parameter
print(name)

print_name('Alex') # 'Alex' is the argument``````
``Alex``

## Positional and keyword arguments

We can pass arguments as positional or keyword arguments. Some benefits of keyword arguments can be:

• We can call arguments by their names to make it more clear what they represent
• We can rearrange arguments in a way that makes them most readable
``````def foo(a, b, c):
print(a, b, c)

# positional arguments
foo(1, 2, 3)

# keyword arguments
foo(a=1, b=2, c=3)
foo(c=3, b=2, a=1) # Note that the order is not important here

# mix of both
foo(1, b=2, c=3)

# This is not allowed:
# foo(1, b=2, 3) # positional argument after keyword argument
# foo(1, b=2, a=3) # multiple values for argument 'a'``````
``````1 2 3
1 2 3
1 2 3
1 2 3``````

## Default arguments

Functions can have default arguments with a predefined value. This argument can be left out and the default value is then passed to the function, or the argument can be used with a different value. Note that default arguments must be defined as the last parameters in a function.

``````# default arguments
def foo(a, b, c, d=4):
print(a, b, c, d)

foo(1, 2, 3, 4)
foo(1, b=2, c=3, d=100)

# not allowed: default arguments must be at the end
# def foo(a, b=2, c, d=4):
#     print(a, b, c, d)``````
``````1 2 3 4
1 2 3 100``````

## Variable-length arguments (`*args` and `**kwargs`)

• If you mark a parameter with one asterisk (`*`), you can pass any number of positional arguments to your function (Typically called `*args`)
• If you mark a parameter with two asterisks (`**`), you can pass any number of keyword arguments to this function (Typically called `**kwargs`).
``````def foo(a, b, *args, **kwargs):
print(a, b)
for arg in args:
print(arg)
for kwarg in kwargs:
print(kwarg, kwargs[kwarg])

# 3, 4, 5 are combined into args
# six and seven are combined into kwargs
foo(1, 2, 3, 4, 5, six=6, seven=7)
print()

# omitting of args or kwargs is also possible
foo(1, 2, three=3)``````
``````1 2
3
4
5
six 6
seven 7

1 2
three 3``````

## Forced keyword arguments

Sometimes you want to have keyword-only arguments. You can enforce that with:

• If you write '`*,`' in your function parameter list, all parameters after that must be passed as keyword arguments.
• Arguments after variable-length arguments must be keyword arguments.
``````def foo(a, b, *, c, d):
print(a, b, c, d)

foo(1, 2, c=3, d=4)
# not allowed:
# foo(1, 2, 3, 4)

# arguments after variable-length arguments must be keyword arguments
def foo(*args, last):
for arg in args:
print(arg)
print(last)

foo(8, 9, 10, last=50)``````
``````1 2 3 4
8
9
10
50``````

## Unpacking into agruments

• Lists or tuples can be unpacked into arguments with one asterisk (`*`) if the length of the container matches the number of function parameters.
• Dictionaries can be unpacked into arguments with two asterisks (`**`) the the length and the keys match the function parameters.
``````def foo(a, b, c):
print(a, b, c)

# list/tuple unpacking, length must match
my_list = [4, 5, 6] # or tuple
foo(*my_list)

# dict unpacking, keys and length must match
my_dict = {'a': 1, 'b': 2, 'c': 3}
foo(**my_dict)

# my_dict = {'a': 1, 'b': 2, 'd': 3} # not possible since wrong keyword``````
``````4 5 6
1 2 3``````

## Local vs global variables

Global variables can be accessed within a function body, but to modify them, we first must state `global var_name` in order to change the global variable.

``````def foo1():
x = number # global variable can only be accessed here
print('number in function:', x)

number = 0
foo1()

# modifying the global variable
def foo2():
global number # global variable can now be accessed and modified
number = 3

print('number before foo2(): ', number)
foo2() # modifies the global variable
print('number after foo2(): ', number)``````
``````number in function: 0
number before foo2():  0
number after foo2():  3``````

If we do not write `global var_name` and asign a new value to a variable with the same name as the global variable, this will create a local variable within the function. The global variable remains unchanged.

``````number = 0

def foo3():
number = 3 # this is a local variable

print('number before foo3(): ', number)
foo3() # does not modify the global variable
print('number after foo3(): ', number)``````
``````number before foo3():  0
number after foo3():  0``````

## Parameter passing

Python uses a mechanism, which is known as "Call-by-Object" or "Call-by-Object-Reference. The following rules must be considered:

• The parameter passed in is actually a reference to an object (but the reference is passed by value)
• Difference between mutable and immutable data types

This means that:

1. Mutable objects (e.g. lists,dict) can be changed within a method.
2. But if you rebind the reference in the method, the outer reference will still point at the original object.
3. Immutable objects (e.g. int, string) cannot be changed within a method.
4. But immutable object CONTAINED WITHIN a mutable object can be re-assigned within a method.
``````# immutable objects -> no change
def foo(x):
x = 5 # x += 5 also no effect since x is immutable and a new variable must be created

var = 10
print('var before foo():', var)
foo(var)
print('var after foo():', var)``````
``````var before foo(): 10
var after foo(): 10``````
``````# mutable objects -> change
def foo(a_list):
a_list.append(4)

my_list = [1, 2, 3]
print('my_list before foo():', my_list)
foo(my_list)
print('my_list after foo():', my_list)``````
``````my_list before foo(): [1, 2, 3]
my_list after foo(): [1, 2, 3, 4]``````
``````# immutable objects within a mutable object -> change
def foo(a_list):
a_list = -100
a_list = "Paul"

my_list = [1, 2, "Max"]
print('my_list before foo():', my_list)
foo(my_list)
print('my_list after foo():', my_list)``````
``````my_list before foo(): [1, 2, 'Max']
my_list after foo(): [-100, 2, 'Paul']``````
``````# Rebind a mutable reference -> no change
def foo(a_list):
a_list = [50, 60, 70] # a_list is now a new local variable within the function
a_list.append(50)

my_list = [1, 2, 3]
print('my_list before foo():', my_list)
foo(my_list)
print('my_list after foo():', my_list)``````
``````my_list before foo(): [1, 2, 3]
my_list after foo(): [1, 2, 3]``````

Be careful with `+=` and `=` operations for mutable types. The first operation has an effect on the passed argument while the latter has not:

``````# another example with rebinding references:
def foo(a_list):
a_list += [4, 5] # this chanches the outer variable

def bar(a_list):
a_list = a_list + [4, 5] # this rebinds the reference to a new local variable

my_list = [1, 2, 3]
print('my_list before foo():', my_list)
foo(my_list)
print('my_list after foo():', my_list)

my_list = [1, 2, 3]
print('my_list before bar():', my_list)
bar(my_list)
print('my_list after bar():', my_list)``````
``````my_list before foo(): [1, 2, 3]
my_list after foo(): [1, 2, 3, 4, 5]
my_list before bar(): [1, 2, 3]
my_list after bar(): [1, 2, 3]``````