Les modes de passage

Il y a trois mode de passage des paramètres :

  • en entrée : valeur transmise par l’appelant, non modifiée par la fonction ;
  • en sortie : valeur transmise par la fonction, récupérée par l’appelant — différant de la valeur renvoyée ;
  • en entrée-sortie.

Dans les langages de programmation, les paramètres sont transmis par valeur pour ceux dont le mode de passage est en entrée et par adresse / référence pour les deux autres modes.

Cas du Python

En Python, le passage de paramètres dépend de la mutabilité des types de données. A ce stade du cours, le seul type mutable connu est le tableau (List), mais les dictionnaires ou les objets sont également mutables.

  • une variable d’un type immuable est transmise par valeur (en entrée donc) ; c’est le cas des types int, float, str, bool et Tuple (qui sont des List non modifiables) ;
  • une variable d’un type mutable est transmise par adresse ; sa modification dans la fonction impacte donc le (sous-)programme appelant.

Transmission par valeur et adresse

Transmission par valeur

Lors d’un paramètres transmis par valeur, la fonction reçoit une copie de la valeur, et les modifications à l’intérieur de la fonction n’affectent pas la variable originale. Exemple :

def fcnExemple1(x: int, s: str):
    x = x + 10    #modification locale seulement
    s = s.upper() #même si les paramètres formels et effectifs ont le même nom
    print("Dans la fonction, x = " + str(x))
    print("Dans la fonction, s = " + s)

if __name__ == "__main__":
    nombre = 5
    s = "original"
    fcnExemple1(nombre, s)
    print("Après l'appel, nombre = " + str(nombre))
    print("Après l'appel, s = " + s)

Transmission par adresse

Lors d’une transmission de paramètre par adresse (comme c’est le cas pour les tableaux du Python, la fonction reçoit une référence vers l’objet original, et les éventuelles modifications affectent la variable originale (ce qui peut être la cause d’effets de bords non désirés).

from typing import List

def fcnExemple2(tableau: List[int]) -> None:
    tableau.append(4)
    tableau[0] = 1
    print("Dans la fonction, tableau = " + str(tableau))

if __name__ == "__main__":
    mon_tab = [0, 2, 3]
    print("Avant l'appel, mon_tab = " + str(mon_tab))
    fcnExemple2(mon_tab) #modifié, même si les paramètres formels
                         #et effectifs ont des nom différents
    print("Après l'appel, mon_tab = " + str(mon_tab))

Réaffectation vs modification

Il est crucial de distinguer la réaffectation (créer un nouvel objet) de la modification (changer l’objet existant). Exemple précédent modifié :

from typing import List

def fcnExemple2(tableau: List[int]) -> None:
    tableau.append(4)     #modification
    tableau = [ 5, 6, 7 ] #réaffectation d'un nouveau tableau
                          #la référence vers le tableau transmis est perdue
    tableau[0] = 1        #modification, du nouveau tableau
    print("Dans la fonction, tableau = " + str(tableau))

if __name__ == "__main__":
    mon_tab = [0, 2, 3]
    print("Avant l'appel, mon_tab = " + str(mon_tab))
    fcnExemple2(mon_tab)  #modifié, même si les paramètres formels
                          #et effectifs ont des nom différents
    print("Après l'appel, mon_tab = " + str(mon_tab))

Dans cet exemple, l’instruction tableau = [ ... ] affecte un nouveau tableau à la variable, dans un autre emplacement mémoire. La référence au paramètre transmis est ainsi perdue.

Autre exemple :

from typing import List

def fcnExemple2(tableau: List[int]) -> None:
    tableau.append(4)     #modification
    param_tab = tableau   #copie de la référence dans une autre variable
    tableau.append(5)     #modifie aussi param_tab (référence mémoire identique)
    print("Addresse de tableau avant réaffectation = " + str(id(tableau)))
    print("Addresse de param_tab = " + str(id(param_tab)))
    tableau = [ 5, 6, 7 ] #réaffectation d'un nouveau tableau
                          #la référence vers le tableau transmis est perdue
    print("Addresse de tableau après réaffectation = " + str(id(tableau)))
    param_tab[0] = 1      #modification, du tableau passé en paramètre
    print("Dans la fonction, tableau = " + str(tableau))
    print("Dans la fonction, param_tab = " + str(param_tab))

if __name__ == "__main__":
    mon_tab = [0, 2, 3]
    print("Avant l'appel, mon_tab = " + str(mon_tab))
    fcnExemple2(mon_tab)  #modifié, même si les paramètres formels
                          #et effectifs ont des nom différents
    print("Après l'appel, mon_tab = " + str(mon_tab))
    print("Addresse de mon_tab = " + str(id(mon_tab)))

Cet exemple montre que l’affectation d’un tableau a une variable (cf param_tab = tableau) est une copie de référence : les deux variables pointent sur le même contenu en mémoire.