Skip to content

Chp E. Les fonctions

1. Le principe

Dans un même programme, une opération, ou une séquence d'opérations peut intervenir à plusieurs reprises. Dans ce cas, il est intéressant de définir une fonction qui exécute ce bloc d'instructions. Découper un programme en fonctions élémentaires permet d'améliorer et de faciliter sa lisibilité. De même lorsqu'on résout un problème, il est souvent intéressant de le scinder en plusieurs petits problèmes (on pensera à l'adage diviser pour régner !). Là encore la notion de fonction prend tout son sens. Nous allons voir dans ce chapitre comment cela s'utilise.

Les fonctions représentent une brique fondamentale de la programmation. Il est important de bien les maîtriser.

Un premier exemple inspiré des mathématiques

On va ici définir une fonction (comme en mathématiques) dont le rôle est de renvoyer le carré du nombre reçu :

def f(x):
    return x*x

print(f"L'image par f de {2} est {f(2)}")

Ce programme affichera `L'image par f de 2 est 2

2. Implémentation en python

On peut imager la notion de fonction en python comme une extension du langage... on enrichit le vocabulaire du langage de base par de nouveaux mots. Une fonction en informatique est assez proche de la notion de fonction mathématique : elle reçoit des arguments (éventuellement aucun), elle les traite et elle renvoie une valeur (éventuellement aucune).

À retenir

On retiendra que l'utilisation des fonctions personnelles en python s'effectue en deux temps.

  1. D'abord on commence par définir la fonction. Pour cela on utilise les mots clés def et  return. À ce stade, le code contenu dans la fonction n'a pas encore été exécuté.

  2. Pour utiliser, ou appeler la fonction on écrit son nom, suivi de parenthèses contenant éventuellement des arguments.

La syntaxe en python pour définir une fonction est la suivante :

def nom_de_la_fonction(liste de paramètres) :
    """ la documentation... (docstring)"""
    Les instructions...
    return valeur de retour

Voyons sur un exemple détaillé les différents point à retenir pour définir une fonction :

# déclaration
def fonction_carree(x : float) -> float :
    """ cette fonction reçoit un flottant et renvoie
    le carré de ce flottant 
    Ex : fonction_carree(2) renvoie 4
    """
    calcul = x*x
    return calcul

# utilisation
print(fonction_carree(2))

On notera :

  • présence du mot clé def
  • ensuite le nom de la fonction (simple mais évocateur, sans accents, sans espaces)
  • ensuite des parenthèses contenant en principe des paramètres (mais les parenthèses peuvent être vides s'il n'y a pas de paramètres)
  • le ou les paramètres, séparés par des virgules. On notera dans l'exemple que l'on a fait suivre le paramètre du symbole ':' et de son type, ceci est facultatif mais c'est une bonne habitude (facilite la lecture entre autre...)
  • le type de la valeur de retour précédé des symboles -> (ceci est aussi facultatif, mais c'est une bonne habitude...)
  • le symbole ":" (impératif, à ne pas oublier)
  • Le contenu de la fonction est indenté.
  • On commence par un texte descriptif aussi détaillé que possible entouré par des triples guillemets (on parle de la docstring)
  • Ensuite on place les instructions à effectuer
  • On termine avec le mot clé return suivi des valeurs de retour.

Application : découpage d'un problème en sous problèmes

Faire le TD E1 : on verra comment découper un problème en petits sous-problèmes à l'aide de la notion de fonction. Le problème abordé est la persistance multiplicative des nombres.

3. Variables locales, variables globales

!!!! Attention, ce point est central et très important à comprendre !!!!

Le principe de base est que toute variable créée à l'intérieur d'une fonction est locale, c'est à dire n'est pas visible depuis l'extérieur de la fonction et heureusement !...

Illustration des variables locales vs globales

def g(): # on définit une fonction g 
    b = 2 # on définit une variable locale b à l'intérieur
    b = b + 10
    print(f"à l'intérieur de g : a={a} et b={b}")

a, b = 1, 1 # on définit a et b à l'extérieur
g() # on appelle la fonction g
print("à l'extérieur de g : a={a} et b={b}")

L'exécution de ce code va afficher :

à l'intérieur de g : a = 1 et b = 12
à l'extérieur de g : a = 1 et b = 1

On constate :

  1. que la variable b à l'intérieur de la fonction g est différente de la variable bà l'extérieur.

  2. que la variable a définie à l'extérieur de la fonction g est tout de même visible depuis l'intérieur de la fonction g mais en lecture uniquement (on ne peut pas la modifier). Si on tente de la modifier, une erreur est levée (UnboundLocalError), car la variable serait locale et globale à la fois, ce qui est problématique (voir ci-dessous une petite subtilité)

Une petite subtilité : une variable ne peut pas être globale et locale à la fois dans une fonction. Heureusement, Python va lever une erreur UnboundLocalError car sinon un code deviendrait vite illisible. Il faut choisir ! Par exemple le code suivant ne fonctionne pas:

def f(): # on définit une fonction f
    a = a + 1 # ici a est locale (à gauche du =) et globale (à dte) !!

a = 2 # a est une variable globale
f() # on appelle la fonction f

L'exécution va donner :

Traceback (most recent call last):
  File "<input>", line 5, in <module>
  File "<input>", line 2, in f
UnboundLocalError: cannot access local variable 'a' where it is
 not associated with a value

Question : comment faire si l'on veut effectivement modifier une variable globale a à l'aide d'une fonction ?

Réponse

On peut procéder en utilisant les paramètres et la valeur de retour :

def incrementer(n : int) -> int :
    """ cette fonction reçoit une nombre entier
    et renvoie cet entier augmenté de 1
    """
    return n+1

a = 2
a = incrementer(a)
# la variable a contient maintenant la valeur 3

Noter que le nom de la variable reçue par la fonction n n'a aucunement besoin d'être le même que celui de la variable globale a et heureusement !

3. Passage des arguments

Attention

Il y a un point important qu'il faut prendre en considération dans python : le passage des arguments aux fonctions se fait par référence, c'est à dire que l'objet passé en argument est partagé avec la fonction (on ne passe que son adresse mémoire) et qu'il risque d'être modifié si on n'en fait pas une copie.

Voyons un exemple pour illustrer cela :

def rallonge(lst : list) -> list :
    """ cette fonction reçoit une liste et y ajoute un élément.
    Attention la liste initiale est modifiée"""
    lst.append("valeur ajoutée")
    # absence de return, la fonction ne renvoie rien...

my_list = ['a','b', 'c']
print(my_list)
rallonge(my_list)
print(my_list)
# sortie :
#['a', 'b', 'c']
#['a', 'b', 'c', 'valeur ajoutée']

On constate qu'après l'appel de la fonction rallonge la liste my_list définie de façon globale a été modifiée ! En effet les mots lst et my_list désignent le même objet (la liste ['a', 'b', 'c']) puisque les arguments sont passés par référence.

Il est déconseillé d'utiliser cette fonctionnalité pour modifier un objet, car cela nuit fortement à la lisibilité du programme. En pratique on préférera faire une copie de la liste en début de fonction, puis renvoyer avec le mot return la nouvelle liste modifiée. On verra cela plus en détails dans le TD E2.

Une petite subtilité (encore !) : le comportement décrit précédemment n'opère que si l'argument est dit mutable (comme une liste ou un dictionnaire). Sinon, quand l'argument est dit immuable (c'est le cas des nombres et des chaînes de caractères)une copie est automatiquement créée par la fonction, et par conséquent la variable ne sera pas modifiée. Voir encore le TD E2 pour des détails.

4. Les TD E