python fonction et tableaux

Identification

Infoforall

13 - 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 01-04-06-07-12-13

Résumé : Version HTML ou fond blanc ou ou PDF (couleur ou gris)

1 - Problématique

Nous allons maintenant voir un point important : l'interaction entre

  • les fonctions et
  • les variables faisant référence à un mutable.

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
  • 60 points de magie
  • 80 points de chance

✎ 01° Que devrait renvoyer l'évaluation de jauges[1] ?

Nous allons déclarer une fonction diminuer_une_jauge, fonction dont voici le prototype :

1
def diminuer_une_jauge(jauges:list, degats:int, index:int) -> bool :

On doit donc lui fournir donc 3 paramètres dans cet ordre :

  • le tableau jauges à modifier,
  • la valeur des degats à soustraire et
  • l'index de la jauge qui doit diminuer.

La fonction doit renvoyer True si la jauge est toujours positive après diminution.

Exemple d'utilisation :

>>> heros = [50, 60, 80] >>> diminuer_une_jauge(heros, 40, 1) True >>> heros [50, 20, 80]

On a bien diminué la jauge d'index 1 qui valait 60 initialement.

02° Que vont contenir les paramètres jauges, degats et index sur cet appel ?

>>> heros = [50, 60, 80] >>> diminuer_une_jauge(hero, 20, 2)

...CORRECTION...

Il faut regarder l'appel et la déclaration de la fonction. Il suffit alors de travailler par identification.

1
def diminuer_une_jauge(jauges:list, degats:int, index:int) -> bool :
>>> diminuer_une_jauge(heros, 20, 2)
  1. Le paramètre jauges va donc recevoir le tableau heros
  2. Le paramètre degats va donc recevoir l'integer 20
  3. Le paramètre index va donc recevoir l'integer 2

03° Expliquer clairement quelle jauge doit diminuer, de combien et le contenu attendu dans le tableau heros après modification.

>>> heros = [50, 60, 80] >>> diminuer_une_jauge(hero, 20, 2)

...CORRECTION...

On veut modifier la jaude d'index 2, celle qui vaut 80 au début. Il s'agit des points de chance.

On veut les diminuer de 20 points.

Le personnage aura donc 60 points de chance après modification.

Une lecture du contenu donnerait ceci :

>>> heros [50, 60, 60]

✎ 04° Cas théorique : que va afficher la console Python lorsqu'on lui demande de fournir le contenu de la variable lePersonnage ?

>>> lePersonnage = [50, 40, 30] >>> diminuer_une_jauge(lePersonnage, 40, 0) True >>> lePersonnage

05° QCM : Sans lancer le programme (qui est non fonctionnel pour l'instant), lire uniquement la documentation de la fonction diminuer_une_jauge et donner le contenu que devra avoir le tableau [5, 2, 10] après exécution de la ligne 12.

  • A : [0, 2, 10]
  • B : [5, -3, 5]
  • C : [5, 2, 5]
  • D : [0, 0, 10]

Faire de même pour la ligne 14.

  • A : [-1, 2, 10]
  • B : [5, -4, 5]
  • C : [5, 2, 4]
  • D : [0, 0, 10]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
def diminuer_une_jauge(jauges, degats, index) : '''Fonction qui modifie jauges[index] en lui enlevant degats si possible La fonction renvoie : False si la jauge voulue est devenue négative, nulle ou que l'index n'existe pas True si la jauge est toujours positive après retrait des dégâts ::param jauges(list) :: le tableau contenant les jauges qu'on désire gérer ::param degats(int) :: le nombre voulu de dégâts à enlever ::param index(int) :: l'index voulu du tableau jauges ::return (bool) :: True si positif et possible, False sinon >>> diminuer_une_jauge([5,2,10], 5, 0) False >>> diminuer_une_jauge([5,2,10], 6, 2) True >>> diminuer_une_jauge([5,2,10], 6, 0) False >>> diminuer_une_jauge([5,2,10], 2, 4) False ''' return False

...CORRECTION...

Après la ligne 12 (diminuer_une_jauge([5,2,10], 5, 0)), le tableau devrait contenir ceci 

[0,2,10] puisqu'on tente de diminuer de 5 points la jauge d'index 0.

La fonction va donc renvoyer False puisque la jauge est devenue nulle.

Après la ligne 14 (diminuer_une_jauge([5,2,10], 6, 2)), le tableau devrait contenir ceci 

[5,2,4] puisqu'on tente de diminuer de 6 points la jauge d'index 2.

La fonction va donc renvoyer True puisque la jauge est toujours positive.

La première chose à faire dans diminuer_une_jauge est de vérifier que l'index fourni par l'utilisateur correspond bien à un index valide. En effet, la seule précondition sur ce paramètre est qu'il doit être un integer. Si l'utilisateur envoie 10000, il a le droit d'après la documentation.

Ici, il faut utiliser un ET (AND en anglais) : pour que le test soit valide, il faut que les deux conditions soient VRAIES en même temps :

1 2 3 4
nombre = len(jauges) # Nombre de jauges différentes dans le tableau # Vérification de la validité de l'index if index >= 0 and index < nombre : # Index valide pass # Les trucs à faire

Ainsi si le tableau contient 3 éléments, les index valides sont bien 0-1-2 mais pas 3 ou 4.

✎ 06° La fonction diminuer_une_jauge fournie ci-dessous ne teste pour l'instant que la validité du paramètre index.

La jauge voulue est donc lisible avec jauges[index] et modifiable avec jauges[index] = 45 par exemple.

Remplacer maintenant la ligne 25 par plusieurs autres lignes de façon à réaliser ceci :

    SI la jauge voulue est initialement > 0

      On diminue cette jauge de la quantité degats

      SI la jauge est toujours > 0 après diminution

        Renvoyer True

      SINON

        Renvoyer False

      Fin SI

    SINON

      Renvoyer False

    Fin SI

Vos modifications devront passer les exemples que le module doctest pourra tester..

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 29 30 31 32 33 34 35 36 37 38 39
def diminuer_une_jauge(jauges, degats, index) : '''Fonction qui modifie jauges[index] en lui enlevant degats si possible La fonction renvoie : False si la jauge voulue est devenue négative, nulle ou d'index impossible True si la jauge est toujours positive après retrait des dégâts ::param jauges(list) :: le tableau contenant les jauges qu'on désire gérer ::param index(int) :: l'index voulu du tableau jauges ::param degats(int) :: le nombre voulu de dégâts à enlever ::return (bool) :: True si c'est positif et possible, False sinon >>> diminuer_une_jauge([5,2,10], 5, 0) False >>> diminuer_une_jauge([5,2,10], 6, 2) True >>> diminuer_une_jauge([5,2,10], 6, 0) False >>> diminuer_une_jauge([5,2,10], 2, 4) False ''' nombre = len(jauges) # Nombre de jauges différentes # Vérification de la validité de l'index if index >= 0 and index < nombre : # Index valide return True # Ecrire des lignes et modifier pour répondre à la question else : return False if __name__ == '__main__' : import doctest doctest.testmod() perso_1 = [50, 60, 80] print(perso_1) disponible = diminuer_une_jauge(perso_1, 40, 1) if disponible == False : print("Personnage indisponible pour le moment") else : print("Personnage ok pour le moment.") print(perso_1)

✎ 07° 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, 40, 1) if disponible == False : print("Personnage indisponible pour le moment") else : print("Personnage ok pour le moment.") print(perso_1)

Vous devriez être parvenu à voir que cela fonctionne. Mais...

Le tableau contenant les jauges du personnage se nomme perso_1 et le paramètre dans la fonction jauges. Comment se fait-il qu'une modification sur jauges_de_la_fonction provoque une modification de perso_1_du_corps_du_programme ?

2 - Identifiant-mémoire des mutables

08° Regarder 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 ?
    • 1 2 3 4 5 6 7 8
      def nouveau(entree) : print(f"Id du paramètre entree : {id(entree)}") input("Appuyer sur ENTREE pour continuer") tableau = [10, 20, 30] print(f"Id de la variable tableau : {id(tableau)}") input("Appuyer sur ENTREE pour continuer") nouveau(tableau)

...CORRECTION...

Voici l'affichage :

Id de la variable tableau : 140225629356232 Appuyer sur ENTREE pour continuer Id du paramètre entree : 140225629356232 Appuyer sur ENTREE pour continuer

On voit bien que les deux variables pointent vers la même zone mémoire.

C'est normal : nous avons transmis le tableau en le placant dans le paramètre de la fonction.

Warning

Cette partie est assez ardue à comprendre mais il vous vous concentrer : de nombreux programmes ne seront compréhensibles que si vous maîtrisez cette partie. Nous réaliserons une activité débranchée pour visualiser cela.

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_une_jauge : elle va modifier l'état du tableau qu'on a fourni.

L'appel envoie la référence-mémoire du tableau perso_1 :

validation = diminuer_une_jauge(perso_1, 40, 1)

Regardons maintenant le prototype de la fonction :

def diminuer_une_jauge(jauges, degats, index) :

On voit qu'avec cet appel, on stocke perso_1 dans le paramètre nommé jauges. C'est comme si vous aviez tapé jauges = perso_1.

Or, la variable perso_1 du programme principal ne contient pas le tableau ! Elle contient l'identifiant-mémoire de la zone où les données du tableau sont stockés. Et du coup, jauges contient également cette adresse mémoire...

Imaginons que dans les deux cas, on y stocke l'adresse &12.

Ainsi, si vous notez

  • perso_1[0] : vous dites d'aller voir à l'adresse référencée par perso_1,: l'adresse &12. Ensuite, on y cherche ce qui est stocké à l'index 0.
  • jauges[0] : vous dites d'aller voir à l'adresse référencée par jauges : l'adresse &12. Ensuite, on y cherche ce qui est stocké à l'index 0.
effet de bord
Visualisation de l'effet de bord

Dans les deux cas, on voit bien qu'on pointe vers l'adresse &100 qui contient 50.

Et c'est pareil en cas de modification du contenu : si l'index [0] du tableau stocké à l'adresse &12 change pour contenir 10 par exemple, cela voudra dire jauges[0] et perso_1[0] vont renvoyer le même contenu : 10.

effet de bord
Visualisation de l'effet de bord

Du coup, on modifie bien les éléments situés dans la variable-tableau globale perso_1 en modifiant les éléments du tableau jauges  !

Documentation : les effets de bords sont à signaler dans la documentation de vos fonctions. Il vaut mieux prévenir les autres que votre fonction va modifier le contenu d'un des paramètres, non ?

09° Vérifier ce que nous venons de dire en utilisant ce code :

1 2 3 4 5 6 7 8 9
def modifier(tableau) : tableau[0] = 'Ca' tableau[1] = 'Alors' tableau[2] = '!' message = ['Ceci', 'au', 'départ'] print(message) modifier(message) print(message)

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', '!']

10° Donner le contenu du tableau notes après exécution du programme.

1 2 3 4 5 6 7 8
def modifier(a, b) : a[0] = 0 b[0] = 20 notes = [12, 15, 8] coeffs = [5, 4, 3] modifier(coeffs, notes)

...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 - Retour au problème

11° Lancer ce code qui permet de modifier correctement la jauge. Vérifier que vous comprennez réellement ce qu'il fait.

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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
def diminuer_une_jauge(jauges, degats, index) : '''Fonction qui modifie la jauge d'index fourni en lui enlevant degats si possible La fonction renvoie : False si la jauge voulue est devenue négative, nulle ou d'index impossible True si la jauge est toujours positive après retrait des dégâts ::param jauges(list) :: le tableau contenant les jauges qu'on désire gérer ::param index(int) :: l'index voulu du tableau jauges ::param degats(int) :: le nombre voulu de dégâts à enlever ::return (bool) :: True si c'est positif et possible, False sinon ..effet de bord :: le tableau jauges risque d'être modifié >>> tableau_test = [5, 2, 10] >>> diminuer_une_jauge(tableau_test, 5, 0) False >>> tableau_test [0, 2, 10] >>> tableau_test = [5, 2, 10] >>> diminuer_une_jauge(tableau_test, 6, 2) True >>> tableau_test [5, 2, 4] >>> tableau_test = [5, 2, 10] >>> diminuer_une_jauge(tableau_test, 6, 0) False >>> tableau_test [-1, 2, 10] >>> tableau_test = [5, 2, 10] >>> diminuer_une_jauge(tableau_test, 2, 4) False >>> tableau_test [5, 2, 10] ''' nombre = len(jauges) # Nombre de jauges différentes dans le tableau # Vérification de la validité de l'index if index >= 0 and index < nombre : # Index valide if jauges[index] > 0 : jauges[index] = jauges[index] - degats if jauges[index] > 0 : return True else : return False else : return False else : return False if __name__ == '__main__' : import doctest doctest.testmod() jauges_perso_1 = [50, 60, 80] print(jauges_perso_1) disponible = diminuer_une_jauge(jauges_perso_1, 40, 1) if disponible == False : print("Personnage indisponible pour le moment") else : print("Personnage ok pour le moment.") print(jauges_perso_1)

Attention pendant la rédaction de vos propres tests : il faut que le rendu soit exactelement celui de la console. Pas d'espace en plus ou en moins.

Ainsi, cette réponse fonctionne : [0, 2, 10]
Alors que celle-ci non : [0,2,10]

Si vous oubliez que la console répond en plaçant un espace après chaque virgule, votre test risque d'échouer "pour rien".

N'oubliez pas non plus de laisser une ligne vide après le dernier test (ligne 33 de la correction).

4 - Bilan

✎ 12° Expliquer le plus clairement possible ce que fait cette fonction sur le tableau qu'on lui passe en argument.

1 2 3
def mystere(tableau) : for index in range( len(tableau) ) : tableau[index] = 10

✎ 13° 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]

La prochaine activité fera le point sur les différentes techniques à utiliser sur les tableaux en fonction des choses qu'on veut faire sur eux.

Comment faire si on veut juste lire un tableau transmis à une fonction ?

Comment faire si on veut modifier un tableau transmis à une fonction ?

Comment faire si on veut créer une copie modifiée ou pas d'un tableau et la faire renvoyer par une fonction ?

Activité publiée le 16 09 2020
Dernière modification : 22 09 2020
Auteur : ows. h.