24 - Tableaux et Fonctions
Nous avons vu trois structures de données indexées :
- Deux non-mutables : le string et le tuple
- Une mutable : le tableau (type list en Python)
Vous allez découvrir la spécificité du transfert d'un type mutable en tant que paramètre de fonction : la fonction peut parvenir à modifier le paramètre !
Logiciel nécessaire pour l'activité : Thonny ou juste Python 3
Evaluation ✎ : questions 05-09-10-11-15-16
Résumé : Version HTML ou fond blanc ou ou PDF (couleur ou gris)
1 - Problématique
Nous allons voir aujourd'hui comment les fonctions peuvent interagir avec les tableaux, implémentés en Python avec le type natif list, une structure de données mutable.
Rappel du comportement d'une fonction sur un paramètre
LECTURE POSSIBLE : lecture d'une variable envoyée depuis le programme principal et placée dans un paramètre
Une fonction peut lire le paramètre sans problème.
1
2
3 |
|
>>> a = 12
>>> lecture(a)
12
L'argument a est envoyé dans le paramètre entree. La fonction parvient à lire 12 en lisant le paramètre entree.
MODIFICATION IMPOSSIBLE : modification d'une variable envoyée depuis le programme principal et placée dans un paramètre
Une fonction ne peut pas modifier une variable du programme principal. Si on réalise une affectation dans la fonction, on ne fait que créer une nouvelle variable locale dans l'espace des noms de la fonction.
1
2
3
4 |
|
>>> a = 12
>>> modification(a)
0
>>> a
12
On renvoie bien le 0 enregistré dans la fonction mais la variable a n'a pas été modifiée.
La seule manière de tenir compte de la réponse de la fonction serait de faire cela :
>>> a = 12
>>> a = modification(a)
>>> a
0
Remarquez bien que cela fonctionne car il y a une affectation dans la console ou le programme. Ce n'est donc pas la fonction qui agit : elle ne fait que transmettre une réponse.
Regardons maintenant le cas des tableaux qu'on envoie en argument lorsqu'on tente d'accéder à leur contenu.
LECTURE POSSIBLE : une fonction peut LIRE une case du tableau qu'elle reçoit en paramètre
Comme auparavant, rien de neuf.
1
2
3 |
|
>>> a = [12, 45, 90]
>>> lecture(a, 0)
12
On rappelle qu'on lit la case numéro index du tableau tableau en tapant tableau[index]
Plus étonnant :
MODIFICATION DU CONTENU POSSIBLE : une fonction peut MODIFIER une case d'un tableau qu'elle reçoit en paramètre
C'est un phénomène totalement nouveau : normalement, on ne peut pas modifier ce qui est contenu dans le programme principal depuis une fonction. Mais avec les tableaux mutables, on va pouvoir modifier le CONTENU du tableau, et pas la variable-tableau elle-même.
1
2
3
4
5
6
7
8
9 |
|
LIGNE 3 : remarquez bien qu'on ne fait pas d'affectation sur tableau mais sur l'une des cases du tableau : entree[index] =
.
Le résultat :
Avant la fonction, entree (du programme) fait référence à [0, 2, 4, 6]
Dans la fonction, entree (de la fonction) fait référence à [10, 2, 4, 6]
Après la fonction, entree (du programme) fait référence à [10, 2, 4, 6]
On voit donc bien qu'à la fin, le contenu de la case d'index 0 du tableau a été modifié.
Nous allons voir pourquoi aujourd'hui.
01° Créer une fonction qui attend un paramètre contenant un tableau et qui modifie toutes ses cases en les multipliant par deux. Comme on doit MODIFIER les cases du tableau, vous utilisez une boucle FOR numérique.
Comme vous avez besoin de connaître le numéro d'index de l'élément pour le modifier, il faut utiliserfor index in range(len(tableau))
avec
- un accès au contenu de la case avec
tableau[index]
- une modification du contenu de la case avec
tableau[index] = nouveau_contenu
Attention : dans l'exemple la variable du tableau se nomme tableau et dans la fonction, le paramètre est nommé entree.
1 |
|
Exemple d'utilisation :
>>> a = [1,10,100]
>>> fois2(a)
>>> a
[2, 20, 200]
...CORRECTION...
1
2
3 |
|
02° La fonction précédente renvoie-t-elle quelque chose qui pourrait justifier la modification de contenu ?
...CORRECTION...
Non, la fonction ne renvoie rien. Le contenu du tableau est bien modifié directement depuis la fonction.
03° Analyser la fonction ci-dessous. Fait-elle la même chose que la fonction précédente ? Pour répondre à cela, demander vous comment l'activer correctement en choisissant entre les deux commandes suivantes :
Commande A :
>>> a = [1,10,100]
>>> fois2(a)
Commande B :
>>> a = [1,10,100]
>>> b = fois2(a)
1
2
3
4
5 |
|
...CORRECTION...
Cette fois, on renvoie un NOUVEAU tableau. Il est basé sur celui qu'on renvoie en entrée et contient deux fois plus dans toutes les cases.
La nuance ? Le tableau entrée n'est pas modifié.
La commande B est donc indispensable si on veut garder la nouvelle version en mémoire :
>>> a = [1,10,100]
>>> b = fois2(a)
04° Encore mieux que la fonction précédente, on peut créer la copie directement et la renvoyer.
1
2
3 |
|
Deux questions
- Un utilisateur pourrait-il voir que les deux fonctions n'ont pas implémenté l'action de la même façon sur les questions 3 et 4 ?
- Comment se nomme la façon de créer la copie ligne 2 ?
...CORRECTION...
Les deux fonctions des questions 3 et 4 renvoient la même sortie. Elles sont juste codées différemment. L'implémentation interne est différente, le résultat est identique.
La méthode de création se nomme création par compréhension.
Imaginons que nous ayons à gérer un jeu où le personnage est défini par trois jauges.
- Le nombre de points de vie est stocké à l'index 0.
- Le nombre de points de magie est stocké à l'index 1.
- Le nombre de points de chance est stocké à l'index 2.
jauges = [50, 60, 80]
veut dire
- 50 points de vie (pv)
- 60 points de magie
- 80 points de chance
✎ 05° Que devrait renvoyer l'évaluation de jauges[1]
?
Pour rappel : jauges = [50, 60, 80]
.
Nous allons déclarer une fonction diminuer_pv, fonction dont voici le prototype :
1 |
|
On doit lui fournir 2 paramètres dans cet ordre :
- le tableau jauges à modifier,
- la valeur des degats à soustraire à l'index 0 (les points de vie)
La fonction doit renvoyer True si les points de vie sont toujours positifs après diminution. Et donc False dans le cas contraire.
Exemple d'utilisation :
>>> heros = [50, 60, 80]
>>> diminuer_pv(heros, 40)
True
>>> heros
[10, 60, 80]
On a bien diminué la jauge d'index 0 de 40 points : on passe de 50 initialement à 10 après diminution. Comme il en reste 10, la fonction répond True.
06° Que vont contenir les paramètres jauges et degats sur cet appel ?
>>> heros = [50, 60, 80]
>>> diminuer_pv(hero, 20)
Pour rappel, voici le prototype de la fonction :
1 |
|
...CORRECTION...
Il faut regarder l'appel et la déclaration de la fonction. Il suffit alors de travailler par identification.
1 |
|
>>> diminuer_pv(heros, 20)
- Le paramètre jauges va donc recevoir la référence du tableau heros
- Le paramètre degats va donc recevoir l'integer 20
07° Expliquer clairement quelle jauge doit diminuer, de combien et le contenu attendu dans le tableau heros après modification.
>>> heros = [50, 60, 80]
>>> diminuer_pv(hero, 20)
...CORRECTION...
On veut modifier la jaude d'index 0, celle qui vaut 50 au début.
On veut les diminuer de 20 points.
Le personnage aura donc 30 points de vie après modification.
Une lecture du contenu donnerait ceci :
>>> heros
[30, 60, 80]
✎ 08° Cas théorique : que va afficher la console Python lorsqu'on lui demande de fournir le contenu de la variable lePersonnage ?
>>> lePersonnage = [80, 40, 30]
>>> diminuer_pv(lePersonnage, 30)
True
>>> lePersonnage
09° Cas théorique : que va afficher la console Python lorsqu'on lui demande de fournir le contenu de la variable lePersonnage ?
>>> lePersonnage = [80, 40, 30]
>>> diminuer_pv(lePersonnage, 90)
???
...CORRECTION...
La fonction va renvoyer False puisque la jauge des points de vie est devenue nulle : 80 - 90 = -10.
10° La fonction diminuer_pv fournie ci-dessous ne fait rien de particulier pour le moment : elle renvoie toujours True...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 | def diminuer_pv(jauges, degats):
'''Fonction qui modifie les pv du personnage en lui enlevant degats et
renvoie
-> True si les pv sont > 0 après réduction
-> False sinon
:: param jauges(list) :: le tableau contenant les jauges du personnage
:: param degats(int) :: le nombre de dégâts à enlever
:: return (bool) :: True si perso actif / False si HS
>>> diminuer_pv([50,20,10], 50)
False
>>> diminuer_pv([50,20,10], 60)
False
>>> diminuer_pv([50,20,10], 40)
True
'''
return True
perso_1 = [50, 60, 80]
print(perso_1)
disponible = diminuer_pv(perso_1, 40)
if not disponible:
print("Personnage indisponible pour le moment")
else:
print("Personnage ok pour le moment.")
print(perso_1)
|
Puisque les jauges du personnage sont stockées dans le paramètre jauges, on peut
- Lire les PV avec
jauges[0]
- Modifier les PV avec
jauges[0] = 45
par exemple.
Question : remplacer maintenant la ligne 19 par plusieurs autres lignes de façon à réaliser ceci :
SI les pv sont > 0
On diminue la jauge des pv de degats
Fin SI
Renvoyer pv > 0
En modifiant les dégats envoyés lors de l'appel test de la ligne 23, vos modifications devront permettre de renvoyer False si les points de vie deviennent nuls ou négatifs.
...CORRECTION...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 |
|
✎ 11° Expliquer, ligne par ligne, ce que fait le programme sur ces lignes. Fournir le résultat affiché si on demande à la console d'afficher quelque chose.
32
33
34
35
36
37
38
39 | perso_1 = [50, 60, 80]
print(perso_1)
disponible = diminuer_une_jauge(perso_1, 70)
if disponible == False:
print("Personnage indisponible pour le moment")
else:
print("Personnage ok pour le moment.")
print(perso_1)
|
2 - Effet de bord
12° Lancer le programme ci-dessous puis répondre à ces questions :
- A quel identifiant-mémoire fait référence la variable tableau ?
- Dans quel paramètre stocke-t-on l'argument tableau lorsqu'on l'envoie ?
- A quel identifiant-mémoire fait référence le paramètre entree ?
- La variable entree du programme principal et la variable entreede la fonction désignent-elles la même zone mémoire ?
1
2
3
4
5
6
7
8 |
|
Remarque : la fonction native input ne sert ici qu'à provoquer une pause dans le déroulement du programme.
...CORRECTION...
Voici un affichage possible :
Id de la variable tableau : 140225629356232
Appuyer sur ENTREE pour entrer dans la fonction
Id du paramètre entree : 140225629356232
Appuyer sur ENTREE pour sortir de la fonction
On voit bien que les deux variables désignent donc la même zone mémoire.
C'est normal : nous avons transmis le tableau en le placant dans le paramètre de la fonction.
Fonction et paramètres mutables : l'effet de bord
En informatique, une fonction est dite à effet de bord si elle modifie l'état d'une structure de données non locale à la fonction elle-même. C'est le cas de la fonction diminuer_pv : elle va modifier l'état du tableau qu'on a fourni.
L'appel envoie la référence-mémoire du tableau perso_1 : diminuer_pv(perso_1, 70)
. Cela veut dire que les deux variables-tableaux désignent la zone-mémoire 12 par exemple.
Voici ce qu'on obtient sur Python Tutor lors de l'appel de la fonction :

C'est assez visuel : les deux variables pointent bien vers la même chose. Les deux noms sont en réalite des alias qui désignent la même chose.
Du coup, si je modifie l'index 0 de jauges, une fois de retour dans le programme principal, je constaterai bien que l'index 0 de perso_1 a changé puisque les deux variables désigent en réalité la même chose !
Documentation : les effets de bords sont à signaler dans la documentation de vos fonctions. Donc, si votre fonction modifie un tableau reçu en paramètre, il faut le signaler dans la documentation.
Une courte explication : le fait qu'en Python, les variables fassent référence à des identifiants mémoires provoquent des effets dont il faut tenir compte.
>>> tableau = [4, 40, 400]
>>> autre = tableau
Aucune des deux variables ne contient vraiment [4, 40, 400]
. En réalité, elles contiennent l'identifiant-mémoire de la zone qui contient [4, 40, 400]
.
>>> id(tableau)
384
>>> id(autre)
384
La conséquence ? Il ne s'agit pas de deux contenus différents mais d'un seul contenu qui possède plusieurs alias. Si on modifie l'un, on modifie l'autre.
>>> autre[0] = 8000
>>> autre
[8000, 40, 400]
>>> tableau
[8000, 40, 400]
C'est le même phénomène avec un tableau envoyé en tant qu'argument à une fonction : le paramètre de réception ne contient pas le contenu du tableau mais sa référence-mémoire. Du coup, modifier le paramètre modifie aussi le tableau d'origine !
13° Vérifier ce que nous venons de dire en utilisant ce code :
1
2
3
4
5
6
7
8
9 |
|
Nous allons donc visualiser que le tableau message contient ceci au début :
['Ceci', 'au', 'départ']
Et ceci après action de la fonction, qui agit pourtant uniquement sur le paramètre tableau, une variable locale à la fonction !
['Ca', 'Alors', '!']
14° Observer le code suivant sur Python Tutor. Donner le contenu du tableau notes après exécution du programme.
1
2
3
4
5
6
7
8 |
|
...CORRECTION...
Lors de l'appel, on envoie le tableau notes en deuxième position.
On stocke donc sa référence dans le deuxième paramètre de la fonction, soit b.

On va donc modifier l'index 0 de b et cela va modifier l'index 0 de notes puisque les deux variables désignent la même adresse-mémoire.
Le tableau notes va donc contenir [20, 15, 8]
à la fin.

3 - Bilan
✎ 15° Expliquer le plus clairement possible ce que fait cette fonction sur le tableau qu'on lui passe en argument.
1
2
3 |
|
✎ 16° Créer une fonction-procédure multiplication qui modifie le tableau par effet de bord en multipliant les éléments du tableau transmis par le second paramètre coef.
Dans votre fonction, il faudra donc lire les index possibles à l'aide d'une boucle FOR numérique (munie d'un range) et modifier les cases une par une à l'aide du nom du tableau dans la fonction et de l'index de travail.
Prototype de la fonction : def multiplication(tableau:list, coef:int) -> None:
.
Exemple d'utilisation :
>>> valeurs = [1, 2, 3, 4]
>>> multiplication(valeurs, 10)
>>> valeurs
[10, 20, 30, 40]
4 - FAQ
Ca à l'air magique l'effet de bord, non ? Ca fonctionne comment ?
L'appel envoie la référence-mémoire du tableau perso_1 : diminuer_pv(perso_1, 70)
. Cela veut dire que les deux variables-tableaux désignent la zone-mémoire 12 par exemple.

Lorsqu'on tape perso_1[0]
, on va donc voir l'index 0 situé à l'adresse &12.
Lorsqu'on tape jauges[0]
, on va donc voir l'index 0 situé à l'adresse &12.
Dans les deux cas, on lit l'adresse &100 qui désigne 50
.
Que se passe-t-il alors lorsque la fonction diminue l'index 0 du tableau jauges avec l'instruction suivante ?
jauges[0] = jauges[0] - degats
On commence par la droite : l'ordinateur récupère donc le 50 et calcule 50-70 ce qui donne -20.
jauges[0] = -20
La variable jauges n'est pas modifiée : elle désigne toujours &12. On va juste aller à l'adresse &12 et modifier la référence mémoire liée à l'index 0 : on place l'adresse &220 qui fait référence à -20 !

Une fois de retour dans le programme principal, que vaut-on obtenir si on veut voir le contenu perso_1[0]
?
On part donc voir l'adresse &12 et on cherche le contenu associé à l'index 0 : la référence-mémoire &220. Et qu'y trouve-t-on ? -20.
Rien de magique en réalité !
Activité publiée le 16 09 2020
Dernière modification : 03 12 2020
Auteur : ows. h.