More on return and function arguments

Resources

Return several variables

Functions can return multiple results, we just need to separate them with commas. Complete the following code.

Tip

Think about how is it possible that all the different options in the following code work. What is going on?

Tip

Under the hood both functions are returning the same, a tuple, an imutable list.

So in the first function “return a, b” is first creating the tuple (a, b), and then returning it.

When we call a function that returns a tuple we can assing the tuple to a variable, like in:

But we can also use the mutiple assignment that we studied for lists and tuples.

Write a function that given a list of numbers it calculates and returns the maximum and the minimum.

Tip

You could iterate through the numbers using a for loop. In each iteration you could check if the number is lower than the current minimum or is larger than the current maximum. In the first iteration the current minimum and maximum could be None.

Tip

Alternatively you could do it with the min and max functions.

In fact Python functions can only return just one thing, an object, one object. Every Python function always returns just one object.

Keyword arguments

So far we have seen that functions can be called by passing arguments to them.

However, you will see another syntax in Python code: keyword arguments.

When we use keyword arguments the order in which the arguments is passed is irrelevant.

You can even mix arguments passed by its order with arguments passed using keywords.

There is only one thing that is not allowed, to pass an argument with no keyword after an argument passed using a keyword.

Default arguments

In function calls it is very common to give, in the function definition, default values to some arguments. These parameters can then be omitted in the function call unless we want to change their values.

References to mutable objects

In Python variables are references to objects. Try to guess what will be the result of running the following code:

Give x a value in order the get the result to be 10. Should x be 4 or 6?

Tip

With lists the behaviour seems different.

Why do we get these different results?

Let’s think about what is going on.

a = "hello"

flowchart TB
    subgraph variables
    a
    b:::invisible
    end
    Memory:::memory
    subgraph Memory
    hello1["'hello'"]
    hello2["'HELLO'"]:::invisible
    end
    a --> hello1
    classDef invisible opacity:0%
    classDef memory fill:#ccc

# a.upper() creates a new string, it does not modify
# the original string that remains intact in memory
# and now b refers to the new string "HELLO"
b = a.upper()

flowchart TB
    subgraph variables
    a
    b
    end
    Memory:::memory
    subgraph Memory
    hello1["'hello'"]
    hello2["'HELLO'"]:::invisible
    end
    a --> hello1
    b --> hello1
    classDef invisible opacity:0%
    classDef memory fill:#ccc

b = a

flowchart TB
    subgraph variables
    a
    b
    end
    Memory:::memory
    subgraph Memory
    hello1["'hello'"]
    hello2["'HELLO'"]
    end
    a --> hello1
    b --> hello2
    classDef invisible opacity:0%
    classDef memory fill:#ccc

With mutable objects, like lists, the result will be different.

a = ["hello", "Jane"]

flowchart TB
    subgraph variables
    a
    b:::invisible
    end
    Memory:::memory
    subgraph Memory
    hello1["['hello', 'Jane']"]
    end
    a --> hello1
    classDef invisible opacity:0%
    classDef memory fill:#ccc

b = a

flowchart TB
    subgraph variables
    a
    b
    end
    Memory:::memory
    subgraph Memory
    hello1["['hello', 'Jane']"]
    end
    a --> hello1
    b --> hello1
    classDef invisible opacity:0%
    classDef memory fill:#ccc

# We are modifying the original list
b[0] = b[0].upper()

# b[0] refers to the string "hello"
# b[0].upper() creates a new string in memory "HELLO"
# so b[0] = b[0].upper() is equivalent to
b[0] = "HELLO"
# We are asking to the list refered by b, the same list that a refers to,
# to change its first member from "hello" to "HELLO"
# So we get a modified list ["HELLO"] and both a and b refer still to this list
# that is in fact the same original list, that is has changed its first member

flowchart TB
    subgraph variables
    a
    b
    end
    Memory:::memory
    subgraph Memory
    hello1["['HELLO', 'Jane']"]
    end
    a --> hello1
    b --> hello1
    classDef invisible opacity:0%
    classDef memory fill:#ccc

This is an aspect that varies from language to language. If you study a different programming language you will need to understand if the variables are ment to be values, pointers or references. In Python they are always references. You can read a Real Python tutorial about Python references.

In Python every variable is a reference, and arguments to functions are thus passed by reference. When we pass a text string or a list to a function Python creates new references to these objects. We have to be careful because if you pass a mutable object to a function the function might changed it.

Changing inside the function a mutable object that have been passed to it without warning the function user is a common source of bugs and problems. This is not a good practice, try to avoid it! The previous code could be written to avoid this problem.

Default mutable arguments

When you create your own functions with default parameters you have to be careful because there is a gotcha. Remember that there are inmutable objects, like str, int or float, and mutable objects, like lists. A list is mutable because we can change its items after we have created it, but a text string is inmutable because once we have created we can not modify it (although be could derive other strings for it).

If you use a mutable object as a default parameter in a function definition, you will get into trouble.

The problem is that the default values for the arguments are created when the function is defined, so just once. Thus, in this example there is only one default empty list for all function calls to the create_guest_list function. Every time that we get the default list we are getting the same empty list. This would not be a problem if the object wasn’t mutable, but it is a huge problem with mutable objects. So the pattern that Python programmers use to avoid this problem is the following: setting the default value to None and creating the list, if needed, inside the function. In this way one different default empty list is created for each function call, not like before that only one was created when the function was defined.