Python Engineer

Free Python and Machine Learning Tutorials

Become A Patron and get exclusive content! Get access to ML From Scratch notebooks, join a private Slack channel, get priority response, and more! I really appreciate the support!

back to course overview

Function arguments - Advanced Python 18

02 Aug 2019

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

Arguments and parameters

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)

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

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.
    • 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.
    • 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[0] = -100 a_list[2] = "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]