Aller au contenu

Les fonctions

Objectifs de la section

2-5 Fonctions
2-6 Conventions du langage de programmation utilisé

Temps requis

20 minutes


Comme vu précédemment, les fonctions (appelées sous-programme dans le livre) permettent d'effectuer certaines manipulations plus complexes, en séparant le code en plus petite unité. Les grandes forces des fonctions sont qu'elles permettent de :

  • Séparer le code en plus petites unités, ce qui augmente la lisibilité et facilite le débogage;
  • Réutiliser le même traitement en appelant la fonction à plusieurs endroit, ce qui diminue l'effort de développement et réduit les erreurs;
  • Encapsuler des traitements pour qu'ils n'aient pas d'effet sur le reste du programme, ce qui réduit les erreurs et augmente la flexibilité et la modularité du code (le code ressemble à des blocs lego que l'on assemble).

Définir une fonction

Section 3.3 / 3.4

Pour définir une fonction, on utilise la syntaxe suivante (noter que ce qui est entre < > est remplacé par un identificateur).

Syntaxe d'une fonction
1
2
3
4
5
6
7
8
def <nom_fonction> ():
    # Instruction de la fonction

def additionner():
    1 + 2

# Appel de la fonction
additionner()

Signature

La ligne débutante par def est appelée signature de la fonction. C'est la seule chose qu'il faut connaître pour utiliser la fonction.

Ajouter des paramètres

La fonction additionner ci-dessous n'est pas très intéressante : elle additionne toujours les mêmes deux nombres ! Une fonction plus utile serait une fonction qui additionne deux nombres quelconques. On doit dans ce cas-là permettre à la fonction de s'exécuter sur diverses valeurs. C'est ce que nous ferons avec les paramètres : ils permettent de transmettre une ou plusieurs valeurs à la fonction. Pour déclarer qu'une fonction accepte des paramètres, on doit les déclarer dans la signature de la fonction, entre les parenthèses.

Syntaxe d'une fonction avec des paramètres
1
2
3
4
5
6
7
8
def <nom_fonction> (<param_1>, <param_2>, ...):
    # Instruction de la fonction

def additionner(operande1, operande2):
    operande1 + operande2

# Appel de la fonction
additionner(1, 2)

Dans l'exemple ci-dessous, le paramètre operande1 prend la valeur 1, lors de son exécution, car le chiffre 1 est passé à la position du paramètre operande1. On peut aussi utiliser des variables; dans ce cas, la valeur de la variable est utilisée.

Syntaxe d'une fonction avec des paramètres
1
2
3
4
nombre_1 = 5
nombre_2 = 8

additionner(nombre_1, nombre_2)     # Réalise l'opération 5 + 8

Les paramètres fonctionnent en tout point comme des variables, sauf que si on les modifie, la nouvelle valeur ne se transmet pas hors de la fonction, comme illustré dans la section sur la portée des variables.

Retourner une valeur

La fonction additionner, bien que plus modulaire n'est pas encore très utile, car on ne peut rien faire avec le résultat de l'opération d'addition. Il serait plus intéressant de récupérer le résultat de l'opération et de pouvoir le réutiliser dans le reste du programme. C'est que l'on peut faire avec l'instruction return.

Syntaxe d'une fonction avec une valeur de retour
def <nom_fonction> (<param_1>, <param_2>, ...):
    # Instruction de la fonction
    return # valeur de retour

def additionner(operande1, operande2):
    somme = operande1 + operande2
    return somme

# Appel de la fonction
resultat = additionner(1, 2)    
# La variable resultat prend la valeur de somme à la fin de la fonction additionner

Travailler avec des fonctions

Maintenant que nous avons vu la syntaxe de base des fonctions, nous verrons deux considérations importantes avec les fonctions, soit la portée de variables et la documentation.

La portée des variables

Section 3.5

Chaque variable est visible (accessible) à l'endroit où elle est déclarée. C'est ce que l'on appelle l'espace de visibilité d'une variable. De plus, la règle qui stipule que chaque identificateur doit être unique s'applique uniquement dans un même espace : deux variables peuvent porter le même nom tant qu'elles ne sont pas dans le même espace. On déclare un nouvel espace de visibilité chaque fois que l'on déclare une fonction. Prenons l'exemple du code suivant, la variable valeur de la fonction n'est pas la même que celle de l'espace global.

Portée des variables
1
2
3
4
5
6
7
8
9
def fonction():
    valeur = 1    
    print(valeur)

valeur = 2
fonction()
print(valeur)

# Affiche 1 puis affiche 2

Également, les changements aux paramètres ne se propagent pas hors de la fonction.

Propagation des changements
1
2
3
4
5
6
7
8
def fonction(valeur):
    valeur = 1

valeur = 2
fonction(valeur)
print(valeur)

# Affiche seulement 2, et ce, même si l'affectation a bien eu lieu

Utiliser les variables globales

Toutefois, on pourrait quand même faire référence à une variable globale dans une fonction, mais cela comporte de hauts risques d'erreurs difficiles à repérer. En effet, rien ne garantit que la variable globale est dans un état cohérent au moment de l'exécution du code.

Utilisation d'une variable globale - avec succès
1
2
3
4
5
6
def fonction():
    terme = 1
    print(terme, valeur)

valeur = 2
fonction()      # Affiche 1 2

En permuttant les lignes 5 et 6, on obtient plutôt une erreur

Utilisation d'une variable globale - avec erreur
1
2
3
4
5
6
def fonction():
    terme = 1
    print(terme, valeur)

fonction()      # Provoque une erreur « NameError: name 'valeur' is not defined »
valeur = 2

La règle à respecter est de ne jamais appeler de variables globales dans une fonction : toujours utiliser des paramètres.

Espace de visibilité

Contrairement à plusieurs autres langages de programme, les instructions conditionnelles et les boucles ne déclarent pas de nouvel espace de visibilité.

Documenter ses fonctions

Documenter ses fonctions

En contexte d'évaluation, il y a toujours des points dédiés à la documentation correcte des fonctions. On peut voir cela comme la méthodologie de l'informatique.

On utilise les docstring pour commenter les fonctions. Une docstring est encapsulée par 3 guillemets anglais « " ». On place la docstring directement sous la signature de la fonction. Elle se compose de trois parties :

  1. La description du but de la fonction. On y explique ce que la fonction fait et non comment elle le fait. Si la description entre sur une ligne, on la place sur la même ligne que les guillemets ouvrants, sinon on commence à la ligne suivante.
  2. Les paramètres de la fonction qui sont introduits par le mot-clé « Paramètre(s) ». Ils ont le format nom -- description. S'il n'y a pas de paramètre, on omet cette partie.
  3. La valeur de retour de la fonction introduite par le mot-clé « Retour ». S'il n'y a pas de valeur de retour, on omet cette partie.
Exemples de docstring
# Bon exemple de docstring
def additionner(operande1, operande2):
    """Calcule la somme de deux nombres.

    Paramètres :
    operande1 -- le premier nombre à additionner
    operande2 -- le second nombre à additionner

    Retour :
    La somme des deux nombres
    """
    somme = operande1 + operande2
    return somme

Exercice

Quels sont les erreurs dans la documentation suivante ?

Exemples de docstring
# Bon exemple de docstring
def multiplication(operande1, operande2):
    """Multiplie operande1 à operande2 puis retourne le résultat et stocke le tout dans une variable appelée produit. Par la suite, la variable produit qui contient 
    la multiplication est retournée par la fonction.

    Paramètres :
    operande1 : le premier nombre à multiplier
    operande2 :  le second nombre à multiplier
    """
    produit = operande1 * operande2
    return produit
Solution

Les quatre erreurs sont :

  1. La description décrit le fonctionnement du code plutôt que d'indiquer son intention
  2. Une description qui prend plusieurs lignes doit commencer sur la ligne en-dessous des guillemets ouvrants
  3. Le symbole entre le nom d'une variable et sa description est --
  4. Il manque la description de la valeur de retour

Structurer un programme

Dans un programme complexe et bien structuré, il y a très peu ou aucun code qui n'est pas dans une fonction. Un programme Python n'est qu'une série de fonctions qui s'appellent entre elles. Pour les programmes plus complexes, on sépare même les fonctions dans plusieurs fichiers, en regroupant les fonctions qui jouent un rôle commun dans un même fichier.

Travailler avec plusieurs fichiers

Un fichier de code Python constitue en fait ... un paquet ! Tant qu'on enregistre les fichiers dans un même répertoire, on peut les importer comme des paquets. Le nom du paquet est simplement le nom du fichier. Par exemple, on code plusieurs fonctions de géométrie (calcul d'aire, d'angle et de périmètre) que l'on enregistre dans un fichier appelé geometrie.py. Dans un autre programme, on pourrait inclure la ligne import geometrie pour pouvoir utiliser les fonctions déclarées dans ce fichier.

Exemple de code partager entre plusieurs fichier
# Fichier geometrie.py
import numpy 

def aire_dique(rayon):
    return numpy.pi * rayon**2

def aire_carre(cote):
    return cote**2

#...

# Fichier programme.py
from geometrie import aire_disque, aire_carre

def calculer_aire_disque():
    print("Quel est le rayon du disque ?")
    rayon = float(input())
    aire = aire_disque(rayon)
    print(f"L'aire du disque de rayon {rayon:.2f} est {aire:.2f}.")

Enregistrer mon fichier au bon endroit

Pour que l'inclusion fonctionne, il faut que les deux fichiers soient enregistrés au même endroit, sinon l'on doit indiquer le chemin relatif vers le fichier inclus. Pour me déplacer dans un chemin relatif, on utilise les symboles suivants :

  • / pour changer de répertoire;
  • .. pour remonter dans un répertoire parent;
  • le nom du répertoire pour accéder à un répertoire enfant.

Supposons que mon fichier à inclure, geometrie.py soit sauvegardé dans le dossier Reutilisable qui est lui-même dans le dossier Projet Python. On travaille dans le fichier programme.py qui est sauvegardé dans le dossier Exercices qui est lui-même dans le dossier Projet Python. Pour accéder à geometrie.py on devrait utiliser l'expression dans le fichier programme.py :

import ../Reutilisable/geometrie

Le premier .. permet d'indiquer qu'on se déplace vers le dossier parent de celui qui contient programme.py soit le dossier Projet Python. Ensuite, on se déplace vers le sous-dossier Reutilisable et finalement on indique le fichier à inclure : geometrie.

Protéger ses fichiers contre les exécutions indésirables

Quand on réutilise des fichiers de code (et c'est une bonne pratique à faire en général), on doit protéger nos fichiers des exécutions accidentelles avec une instruction spéciale. Si on ne le fait pas, tout le code global du fichier est exécuté à chaque fois que le fichier est importé ! Ce comportement est source d'erreurs, souvent étranges et difficiles à repérer. Chaque fichier qui contient du code global devrait définir cette instruction qui permet de préciser le point d'entrée du programme.

Point d'entrée du programme
if __name__ == "__main__":
    # Ici on place le code « global »

Cette instruction assure d'exécuter le code global seulement si ce fichier est le point d'entrée de notre programme; s'il est importé dans un autre programme, alors on ne l'exécute pas. C'est reconnu comme une bonne pratique à faire systématiquement.

De plus, on retrouve souvent une seule instruction dans ce bloc : l'appel à la fonction qui gère le programme.

Exemple de fichier pour faire des exercices

Voici un exemple de structure de fichier pour réaliser tous vos exercices d'une même section dans un même fichier ou du moins, plusieurs exercices dans un même fichier. Faites attention de ne pas vous perdre dans votre code !

Regrouper des exercice
def exercice_1_1():
    # Code de l'exercice

def exercice_1_2():
    # Code de l'exercice

# ...

if __name__ == "__main__":
    exercice_1_1()
    # exercice_1_2()
    # exercice_1_3()
    # ...

En commentant le bon appel de fonction, cela vous permet de tester un exercice à la fois. De plus, l'encapsulation des fonctions assure que vos variables ne soient pas partagées entre plusieurs exercices, tant que vous utilisez toujours des variables locales.

Exercices

Page 56 et suivantes

Recommandés : 3.1, 3.2, 3.7, 3.14, 3.1, 3.16, 3.17, 3.18, 3.19, 3.21

Page 70 et suivantes

Recommandés : 4.12, 4.15, 4.17