Function arguments

Resources

Keyword arguments

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

However, you will see another syntax in Python code, using 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 default values to some arguments. These parameters can then be omitted in the function call unless we want to change their values.

Default mutable arguments

When you create your own function 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 list. A list is mutable because we can change its members 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 was created when the function was built.

References to mutable objects

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 this 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.