Functions

Author

Ludovic Deneuville

Before starting

1 Functions

The idea of a function is to group together pieces of code that could be used in several places in your program. Using functions is a good practice:

  • it reduces code duplication
  • it allows you to better structure your code and make it clearer

A function is composed of:

  • a set of parameters
  • instructions that use the parameters
  • returns or displays a result

The def keyword is used to define a function. Here is an example of a function:

  • Function name: ma_fonction
  • Parameters: 2 parameters p1 and p2
  • Output: result of the operation p1 x p2 + p1 + 5
def ma_fonction(p1, p2):
    resultat = p1 * p2 + p1 + 5
    return resultat

Now that our function is defined, we can call it as many times as we want

ma_fonction(2, 6)
ma_fonction(5, 1) + ma_fonction(8, 2)

1.1 Arguments

Arguments are the parameters of the function. When we call a function by specifying arguments, we say that we “pass” arguments to it. These arguments then become variables that can only be used inside the function.

def maximum(a, b):
    if a > b:
        resultat = a
    else:
        resultat = b
    return resultat
maximum(1, 5)

Outside functions, arguments no longer exist and are no longer known.

a

Positional arguments and keyword arguments

In Python, functions support two ways of passing arguments:

  • positional arguments: arguments are passed in the order in which they were defined
  • keyword arguments: the parameter name is specified when passing the argument

Let’s illustrate this difference using a function that simply performs a division.

def division(x, y):
    if y == 0:
        print("ERROR: Division by 0 is impossible")
    else:
        return x / y
# Positional arguments
division(8, 2)
# Keyword arguments
division(x=8, y=2)

In the case of positional arguments, respecting the order is mandatory.

print(division(0, 5))
print(division(5, 0))

We notice that None is displayed above. The explanation is that when we enter the if y == 0 part of the code, there is no return. Therefore, by default, the method returns None, which represents the absence of a value.

In the case of keyword arguments, the order no longer matters.

print(division(x=0, y=5))
print(division(y=5, x=0))

Required arguments and optional arguments

When defining a function, it is common to want to combine:

  • arguments that the user must absolutely specify
  • optional arguments that specify a default behavior of the function, but can also be modified if necessary

Let’s look at how we can modify the behavior of the print function using an optional argument.

print("salut")
print("salut")
print("salut", end=' ')
print("salut")

We modified the behavior of the first print call using the optional parameter end. By default, this value is set to '\n', meaning a line break. We changed it in the second cell to a space, hence the difference in output.

We will now create a function with an optional argument. To explain the behavior of this method, documentation has been added between the characters """.

def note_finale(note1, note2, bonus=0) -> float:
    """Fonction d'ajout de 2 notes
    Parameters
    ----------
        note1 : float
            la première note
        note2 : float
            la deuxième note
        bonus : float
            un bonus (optionnel, par défaut égal à 0)
    Returns
    -------
        float : sommes des 2 notes et du bonus
    """
    return note1 + note2 + bonus
# Default behavior (bonus=0)
note_finale(8.5, 7)
# Modified behavior
note_finale(8.5, 7, bonus = 2)

You will also notice that the function signature ends with -> float.

This is also a non-binding documentation element. It indicates the expected data type of the output.

Bonus: variable number of arguments

  • The *args notation allows a function to receive a variable number of positional arguments
  • The **kwargs notation allows a function to receive a variable number of key-value arguments
# Example using args

def moyenne(*args):
    somme = 0
    nb = 0
    for a in args:
        somme += a
        nb += 1
    print(f"Moyenne de {args} : {somme / nb}")

moyenne(10, 15)
moyenne(8, 20, 16, 12)
# Example using kwargs

def recette(**kwargs):
    for a in kwargs:
        # get argument name
        arg_name = a
        # get argument value
        arg_value = kwargs[arg_name]
        print(arg_name, " = ", arg_value)
    
    
recette(tomate=2, farine="100g", sel=True)

1.2 Results

Principle

We have seen:

  • that every function returns an output result
  • that the return statement is used to specify this result

When the function is called, it is evaluated to the value specified by return, and this value can then be stored in a variable and used in subsequent calculations, and so on.

def division(x, y):
    return x / y
a = division(4, 2)
b = division(9, 3)
division(a, b)  # 2 / 3

Important note: when a return statement is reached in a function, the rest of the function is not executed.

def test(x):
    return x
    print("will I be displayed?")
    
test(3)

Returning multiple results

A function returns by definition one result, which can be any Python object. What should we do if we want to return several results? We can simply store the different results in a container (list, tuple, dictionary, etc.), which can itself contain a large number of objects.

def calculs_mathematiques(a, b):
    somme = a + b
    difference = a - b
    produit = a * b
    return somme, difference, produit

resultats = calculs_mathematiques(10, 5)

print(resultats)
type(resultats)

By default, multiple returns are tuples. But it is also possible to return a list or a dictionary.

def puissance_liste(a):
    return [a**2, a**3]

puissance_liste(4)
def puissance_dico(nombre):
    carre = nombre ** 2
    cube = nombre ** 3
    return {"carre": a**2, "cube": a**3}

puissance_dico(4)

1.3 Lambda functions

There is another concise way to define a simple function: the lambda function.

carre = lambda x: x**2

carre(6)

2 Exercises

2.1 Exercise 1

Create a puissance function that takes two numbers x and y as input and returns the power function \(x^y\).

# Test your answer in this cell

2.2 Exercise 2

Write a statistiques_descriptives function that:

  • takes a list of numbers as input
  • returns the mean and variance
# Test your answer in this cell

2.3 Exercise 3

Write an est_pair function that:

  • takes one parameter as input
  • returns a boolean indicating whether this parameter is even

Add a test to check that the parameter is an integer.

# Test your answer in this cell

2.4 Exercise 4

Write a function that:

  • takes a list of any elements as input
  • returns a new list containing the unique elements from the initial list
  • allows, through an optional parameter, to sort or not sort the final list in alphanumeric order (the default behavior is not to sort).
# Test your answer in this cell

2.5 Exercise 5

Recursive functions are functions that call themselves within the body of the function, which leads to infinite calls until reaching a stopping criterion (see the Pascal triangle example below).

Implement the factorial function recursively.

def triangle_pascal(n):
    if n == 0:
        return [[1]]                                          # Stopping condition
    else:
        triangle = triangle_pascal(n - 1)                     # Recursive call to obtain previous rows
        prev_row = triangle[-1]                               # Retrieve the last generated row
        new_row = [1]                                         # First element of the new row
        # Calculate the elements of the new row
        for i in range(len(prev_row) - 1):
            new_row.append(prev_row[i] + prev_row[i + 1])  
        new_row.append(1)                                     # Last element of the new row
        triangle.append(new_row)                              # Add the new row to the triangle
        return triangle

print('\n'.join(['\t'.join(map(str, row)) for row in triangle_pascal(10)]))
# Test your answer in this cell

2.6 Exercise 6

Write an appliquer_fonction_liste function that:

  • takes as parameters:

    • a list of integers
    • a function
  • returns the list to which the function has been applied

Example:

appliquer_fonction_liste([1, 2, 3, 4], lambda x: x**2) -> [1, 4, 9, 16]

# Test your answer in this cell