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.
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,booletTuple(qui sont desListnon 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
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.