python fonction et tableaux

Identification

Infoforall

12 - Fonctions et mutables


Nous avons vu trois structures de données indexées :

  • Deux immuables : le string (type str en Python) et le tuple (type tuple en Python)
  • Une mutable : le tableau statique (type list en Python)

Vous allez découvrir la spécificité du transfert d'un argument de type mutable dans un paramètre de fonction : la fonction peut parvenir à modifier le contenu du paramètre muable !

Exercices supplémentaires 🏠 : sur le site

Logiciel nécessaire pour l'activité : Thonny ou juste Python 3

Evaluation ✎ : questions 05-09-10-11-15-16

Documents de cours PDF : .PDF

Sources latex : .TEX et entete.tex et licence.tex

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.

1.1 FONCTION : paramètre référençant un type simple

LECTURE POSSIBLE

On peut accèder en lecture à un contenu placé dans un paramètre. Sinon, tout serait un peu compliqué...

1 2 3 4 5 6
def lecture(entree): """La fonction parvient à LIRE le paramètre""" return entree a = 12 print( lecture(a) )
12

L'argument a est envoyé dans le paramètre entree. La fonction parvient à lire le contenu 12 en accèdant au paramètre entree.

MODIFICATION IMPOSSIBLE par nouvelle affectation depuis la fonction

Par contre, la modification du contenu d'un type simple reçu dans un paramètre n'est pas possible.

Une fonction ne peut pas modifier le contenu d'une telle variable du programme principal. Si on réalise une affectation dans la fonction, on crée simplement une variable locale dans l'espace des noms de la fonction.

1 2 3 4 5 6 7 8
def modification(entree): """La fonction ne parvient pas à MODIFIER l'argument stocké dans le paramètre entree""" entree = 0 # Affectation donc nouvelle variable locale ! return entree # Renvoi du contenu local a = 12 print( modification(a) ) print( a )
0 12

On renvoie bien le 0 référencé par la variable locale de la ligne 3 mais la variable globale a n'a pas été modifiée.

Modification par nouvelle affectation dans le programme lui-même

La seule manière de tenir compte de la réponse de la fonction serait de faire cela :

1 2 3 4 5 6 7 8 9
def modification(entree): """La fonction ne parvient pas à MODIFIER l'argument stocké dans le paramètre entree""" entree = 0 # Affectation donc nouvelle variable locale ! return entree # Renvoi du contenu local a = 12 a = modification(a) print( a )
0

Remarquez bien que cela fonctionne car il y a une affectation dans le programme principal. Ce n'est pas la fonction "qui agit" : elle ne fait que transmettre une réponse et c'est le programme qui réalise l'affectation.

Il ne s'agit donc pas d'une modification depuis la fonction.

Regardons maintenant le cas du tableau statique (gérés en Python par le type list) envoyé en tant qu'argument.

1.2 FONCTION : paramètre référençant un tableau

LECTURE POSSIBLE

On peut accèder en lecture à un contenu placé dans un paramètre-tableau. Sinon, tout serait un peu compliqué...

1 2 3 4 5 6
def lecture(tableau:list[int], i:int) -> int: """La fonction parvient à LIRE la case i du tableau-paramètre""" return tableau[i] a = [20, 10, 30] print( lecture(a, 2) )
30

L'argument a est envoyé dans le paramètre tableau. La fonction parvient à lire le contenu de la case 2 du tableau, à savoir 30.

MODIFICATION POSSIBLE du CONTENU

Plus étonnant : la modification du contenu d'un type construit mutable reçu dans un paramètre est possible.

Notez bien qu'on modifie le contenu t[i] des cases, pas le conteneur t lui-même.

1 2 3 4 5 6 7 8
def mise_a_0(tableau:list[int], i:int) -> None: """Modifie la case i en y plaçant 0""" tableau[i] = 0 # Modification de la case i, pas du tableau ! # Aucun retour, donc équivalent à return None a = [20, 10, 30] mise_a_0(a, 2) print(a)
[20, 10, 0]

Le tableau a bien été modifié dans la fonction et la modification est bien effective puisqu'elle apparaît encore dans le programme principal.

Pourquoi est-ce que cela fonctionne ?

L'explication arrivera plus loin dans l'activité. Pour le moment, retenons qu'en Python :

  • Une fonction ne peut pas modifier une variable globale passée en paramètre.
  • Une fonction peut modifier le CONTENU d'une variable mutable passée en paramètre.
  • Bien entenu, une fonction ne peut pas modifier le CONTENU d'une variable immutable passée en paramètre !

01-A° Réaliser une fonction qui reçoit un tableau et qui modifie toutes ses cases en les multipliant par deux. On utilisera une boucle for avec indice puisqu'on désire MODIFIER le contenu.

1 2 3 4 5 6 7 8 9 10 11
def fois2(t:list) -> None: pass a = [10, 20, 30] print("a avant action de la fonction :") print(a) fois2(a) print("a après action de la fonction :") print(a)

Voici le résultat attendu dans la console :

a avant action de la fonction : [10, 20, 30] a après action de la fonction : [20, 40, 60]

...CORRECTION...

1 2 3
def fois2(t:list) -> None: for i in range(len(t)): t[i] = t[i] * 2

01-B° Répondre à ces questions liées à la fonction précédente :

  1. La fonction précédente renvoie-t-elle quelque chose qui pourrait justifier la modification du contenu ?
  2. Quel est le coût de votre fonction ?

...CORRECTION...

  1. Non, la fonction ne renvoie rien. Le contenu du tableau est bien modifié directement depuis la fonction.
  2. On notera 𝑛 le nombre de cases du tableau.
  3. 1 2 3
    def fois2(t:list) -> None: for i in range(len(t)): t[i] = t[i] * 2
    • L'évaluation t[i] * 2 est à coût constant (ne dépend pas de la taille du tableau).
    • L'affectation t[i] = ... est à coût constant.
    • En Python, la fonction native len() est à coût constant et on ne l'évalue ici que lors du démarrage de la boucle.
    • On réalise la boucle L2-L3 𝑛 fois, on peut en conclure que la fonction est à coût linéaire.

02° Analyser les instructions internes de la fonction ci-dessous. Fait-elle la même chose que la fonction précédente ? En quoi la documentation est-elle claire à ce propos ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
def fois2(t:list) -> list : copie = [v for v in t] for i in range(len(copie)): copie[i] = copie[i] * 2 return copie a = [10, 20, 30] print("a avant action de la fonction :") print(a) b = fois2(b) print("a après action de la fonction :") print(a) print("b après action de la fonction :") print(b)
a avant action de la fonction : [10, 20, 30] a après action de la fonction : [10, 20, 30] b après action de la fonction : [20, 40, 60]

...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 reçu en paramètre n'est pas modifié.

D'ailleurs, la documentation rapide des types indique clairemnet que la fonction va renvoyer un type list.

03° Encore mieux que la fonction précédente, on peut créer la copie directement et la renvoyer.

1 2
def fois2(t:list) -> list : return [v*2 for v in t]

Questions

  1. Un utilisateur pourrait-il voir que les deux fonctions des questions 2 et 3 n'ont pas implémenté l'action de la même façon ?
  2. Comment se nomme la façon de créer la copie de la ligne 2 ?
  3. Traduire la signification de l'expression derrière le return.

...CORRECTION...

Les deux fonctions des questions 2 et 3 renvoient la même sortie. L'implémentation interne est différente, le résultat est identique. L'utilisateur ne pourra se rendre compte de rien, surtout que les deux fonctions ont un coût qui reste linéiare.

La méthode de création se nomme création par compréhension.

  • Crée un nouveau tableau qui, pour chaque valeur contenue dans t, possède une case qui contient le double de cette valeur.
  • 2 - Exercice

    Imaginons que nous ayons à gérer un jeu où le joueur manipule un vaisseau spacial défini par trois caractéristiques placées dans un tableau :

    • Le nombre de points de résistance est stocké via l'indice 0.
    • En abscisse, la cordonnée x, stockée via l'indice 1 du tableau.
    • En ordonnée, la cordonnée y, stockée via l'indice 2 du tableau.

    Exemple

    vaisseau = [50, 60, 80] veut dire :

    • résistance = 50
    • x = 60
    • y = 80

    ✎ 04° On considère vaisseau = [50, 60, 80]. Que devrait renvoyer l'évaluation de vaisseau[1] ? De quelle information s'agit-il ?

    Explications liées à l'exercice

    Nous allons définir une fonction diminuer_protection(), fonction dont voici le prototype :

    1
    def diminuer_protection(v:list[int], degats:int) -> bool:

    On doit lui fournir 2 paramètres dans cet ordre :

    • le tableau v modélisant le vaisseau,
    • la valeur des degats à soustraire à l'indice 0 (la résistance du vaisseau)

    La fonction doit renvoyer True si les points de résistance sont supérieurs à 0 après diminution. Et False dans le cas contraire.

    Exemple d'utilisation :

    >>> vaisseau = [50, 60, 80] >>> diminuer_protection(vaisseau, 40) True >>> vaisseau [10, 60, 80]

    On a bien diminué la valeur d'indice 0 : on passe de 50 initialement à 10 après diminution. Comme il en reste 10, la fonction répond True.

    05° Que vont contenir les paramètres v et degats sur cet appel à la fonction ?

    >>> vaisseau = [50, 60, 80] >>> diminuer_protection(vaisseau, 20)

    Pour rappel, voici le prototype de la fonction :

    1
    def diminuer_protection(v:list[int], degats:int) -> bool:

    ...CORRECTION...

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

    1
    def diminuer_protection(v:list, degats:int) -> bool:
    >>> diminuer_protection(vaisseau, 20)
    1. Le paramètre v va donc recevoir la référence du tableau vaisseau
    2. Le paramètre degats va donc recevoir l'integer 20

    06° Expliquer clairement quelle case doit diminuer, de combien et le contenu attendu dans le tableau vaisseau et la variable operationnel après modification.

    >>> vaisseau = [50, 60, 80] >>> operationel = diminuer_protection(vaisseau, 20)

    ...CORRECTION...

    On veut modifier la case d'index 0, celle qui vaut 50 au début.

    On veut les diminuer de 20 points.

    Le vaisseau aura donc 30 points de résistance après modification. C'est supérieur à 0, la fonction a donc renvoyé True.

    Une lecture du contenu donnerait ceci :

    >>> vaisseau [30, 60, 80] >>> operationnel True

    ✎ 07° Que va afficher la console Python lorsqu'on lui demande de fournir le contenu de la variable vaisseau2 et op2 ?

    >>> vaisseau2 = [80, 40, 30] >>> op2 = diminuer_protection(vaisseau2, 30) >>> vaisseau2 ??? >>> op2 ???

    08° Que va afficher la console Python lorsqu'on lui demande de fournir le contenu de la variable vaisseau3 ?

    >>> vaisseau3 = [80, 40, 30] >>> op3 = diminuer_protection(vaisseau3, 90) >>> vaisseau3 ??? >>> op3 ???

    ...CORRECTION...

    La fonction va renvoyer False puisque la protection est devenue inférieure à 0 : 80 - 90 = -10.

    09° Compléter la fonction diminuer_protection() pour qu'elle réalise ceci :

      On diminue la protection de degats

      SI la protection est < 0

        protection référence 0

      Fin SI

      Renvoyer protection > 0

    On renvoie bien un boléen : protection > 0.

    1 2
    def diminuer_protection(v:list[int], degats:int) -> bool: pass

    ...CORRECTION...

    1 2 3 4 5 6 7
    def diminuer_protection(v:list[int], degats:int) -> bool: """Modifie la protection, renvoie True si > 0 après réduction""" v[0] = v[0] - degats if v[0] < 0: v[0] = 0 return v[0] > 0

    ✎ 10° 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.

    1 2 3 4 5 6 7 8 9 10 33 34 35 36 37 38 39
    def diminuer_protection(v:list[int], degats:int) -> bool: """Modifie la protection, renvoie True si > 0 après réduction""" v[0] = v[0] - degats if v[0] < 0: v[0] = 0 return v[0] > 0 vaisseau = [50, 60, 80] print(vaisseau) operationnel = diminuer_protection(vaisseau, 70) if operationnel == False: print("Vaisseau indisponible pour le moment") else: print("Vaisseau ok pour le moment.") print(vaisseau)

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

    Le tableau contenant les caractéristiques se nomme vaisseau et le paramètre dans la fonction v. Comment se fait-il qu'une modification sur v_de_la_fonction provoque une modification de vaisseau_du_corps_du_programme ?

    L'explication dans la partie suivante.

    3 - Effet de bord : l'explication sur les modifications d'un mutable

    Commençons par quelques rappels qui seront fondamentaux pour comprendre comment fonctionne l'interaction entre une fonction et un paramètre référençant un type mutable :

    (Rappel) 3.1 Variables en Python : l'espace des noms

    Lors d'une affectation, Python crée une association entre le nom de la variable et un contenu en mémoire.

    une sorte de liaison

    L'espace des noms correspond au mécanisme qui lie le nom de la variable à un contenu mémoire, représenté ici par le simple trait entre les deux.

    (Rappel) 3.1.1 L'espace des noms de Python

    Les associations entre nom et contenu sont mémorisées dans une table qu'on nomme l'espace des noms.

    Imaginons qu'on dispose de ce programme :

    1 2 3
    a = 10 b = 20 c = b - a

    Voici l'image simpliste qu'on peut se faire de la liaison entre l'espace des noms et l'espace mémoire :

    liaison magique espace des noms et mémoire

    Cette liaison est réalisée en associant dans une table chaque nom de variable à une zone mémoire.

    espace des noms type simple

    En Python, on peut récupérer l'adresse / référence / identifiant à l'aide de la fonction native id().

    >>> a = 10 >>> b = 20 >>> id(a) 108 >>> id(b) 524
    (Rappel) 3.1.2 LES espaceS des noms de Python

    Il existe en réalité plusieurs espaces des noms pouvant référencer l'espace mémoire :

    • L'espace des noms global et permanent du programme : il existe pendant tout le déroulement du programme.
    • Chaque appel de fonction crée un espace des noms local temporaire qui est détruit après l'appel.

    On peut voir visuellement ces zones dans Python Tutor. La zone grise est l'espace des noms actuellement utilisé par Python.

    Exemple avec ce court programme :

    1 2 3 4 5
    def f(x): return x * 2 a = 10 b = f(a)
    espaces des noms
    (Rappel) 3.1.3 Utilisation par Python

    Lorsque Python doit évaluer une variable dans une fonction :

    1. Il commence par chercher dans l'espace local.
    2. Si il ne trouve pas, il cherche ensuite dans l'espace global.
    3. S'il ne trouve toujours pas de variable portant ce nom, il déclenche une exception NameError.

    Traduit en Python, cela donnerait quelque chose comme ceci :

    1 2 3 4 5 6 7 8 9 10 11 12 13
    def evaluer_variable(nom): espace_local = locals() # espace local actuel sous forme d'un dico espace_global = globals() # idem mais pour l'espace global if nom in espace_local: # si le nom est bien une clé de l'espace local return espace_local[nom] # on renvoie son contenu mémoire elif nom in espace_global: # sinon si le nom est une clé de l'espace global return espace_global[nom] # on renvoie son contenu mémoire else: # sinon, raise NameError # on lève l'exception NameError
    ATTENTION

    Dans d'autres langages (comme le C par exemple), l'adresse d'une variable ne change pas après déclaration. C'est bien le contenu de la case mémoire qui change.

    (Rappel) 3.2 TABLEAU STATIQUE : définition

    Un tableau statique est un tableau comportant un nombre de cases fixé à la création.

    Un tableau est un conteneur formant une collection ordonnée d'éléments ayant tous le même type. On peut donc avoir un tableau statique d'integers, ou un tableau statique de floats par exemple.

    Les cases sont identifiées par un numéro nommé indice (index en anglais).

    Voici deux exemples de tableaux :

    Un tableau de caractères de 3 cases (indices 0-1-2) 

    Indice 0 1 2
    Elément 'A' 'B' 'C'

    Un tableau de flottants de 4 cases (indices 0-1-2-3) :

    Indice 0 1 2 3
    Elément 5.89 12.56 15.89 5.0
    flowchart LR D([Variable]) --> M([conteneur-tableau]) M -- indice 0 --> A([élément 0]) M -- indice 1 --> B([élément 1]) M -- indice 2 --> C([élément 2])

    Notez bien que la première case est la case d'indice 0, pas celle d'indice 1. Si un tableau possède 20 cases, les indices disponibles vont de 0 à 19.

    Regardons d'abord comment visualiser dans Python le lien entre un nom de variable et cette zone mémoire :

    3.3 Variable : fonction native id()

    La fonction native id() renvoie l'identifiant de la zone-mémoire qui est associée à une variable dans l'espace des noms.

    espace des noms type simple
    >>> a = 10 >>> id(a) __108 >>> id(10) __108

    Votre propre valeur peut être différente bien entendu.

    CPython

    Thonny utilise une implémentation de Python réalisée en C. La fonction id() renvoie alors réellement l'adresse-mémoire.

    11° Lancer le programme ci-dessous puis répondre aux questions :

    1 2 3 4 5 6 7 8 9
    t1 = [10, 20, 30] t2 = t1 t3 = [v for v in t1] print(id(t1), " --->", t1) print(id(t2), " --->", t2) print(id(t3), " --->", t3)
    • Expliquer pourquoi on peut affirmer que le tableau t2 est un alias du tableau t1,
    • Expliquer pourquoi on peut affirmer que le tableau t3 est lui une copie du tableau t1.
    • Réflechir à ce que donnerait ces instructions (puis vérifier dans la console directement).
    • >>> t2[2] = 100 >>> t1 ??? >>> t2 ??? >>> t3 ???

    ...CORRECTION...

    Voici un affichage possible (vos propres valeurs peuvent varier) :

    140122459270016 ---> [10, 20, 30] 140122459270016 ---> [10, 20, 30] 140122457399424 ---> [10, 20, 30]

    On voit donc clairement que t et t2 pointent vers la même zone mémoire, là où t3 est une copie indépendante.

    De façon imagée, cela donne ceci :

    Copie et alias
    >>> t2[2] = 100 >>> t1 [10, 20, 100] >>> t2 [10, 20, 100] >>> t3 [10, 20, 30]

    On constate bien que modifier le contenu pointé par t2 modifie également le contenu pointé par t1 puisque les deux variables sont des alias vers le même contenu, alors que le contenu de t3 reste lui inchangé puisqu'il s'agit d'une copie.

    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 entree de la fonction désignent-elles la même zone mémoire ?
      • 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 sortir de la fonction") tableau = [10, 20, 30] print(f"Id de la variable tableau : {id(tableau)}") input("Appuyer sur ENTREE pour entrer dans la fonction") nouveau(tableau)

        Remarque : la fonction native input() ne sert qu'à provoquer une pause dans le déroulement du programme. Il faut appyer sur ENTREE pour valider la suite.

    ...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 la même zone mémoire puisque l'identifiant est le même.

    On peut donc en déduire qu'en Python, on ne transmet pas le contenu du tableau mais juste sa localisation en mémoire.

    13° Voici un programme :

    1 2 3 4 5 6 7 8 9
    def modifier(t): t[0] = 'Ca' t[1] = 'Alors' t[2] = '!' message = ['Ceci', 'au', 'départ'] print(message) modifier(message) print(message)
    • Donner les lignes de code suivies par l'interpréteur et le contenu du tableau globale au fur et à mesure de l'exécution.
    • Vérifier ensuite en utilisant ce code sur le site Python Tutor : vous devriez visualiser que la fonction parvient bien à agir sur le CONTENU du tableau global puisqu'on lui transmet sa référence dans le paramètre.

    ...CORRECTION...

    • L1 : défintion de la fonction.
    • L6 : la variable globale message référence le contenu ['Ceci', 'au', 'départ'].
    • L7 : affiche ['Ceci', 'au', 'départ']
    • L8 : appel à la fonction en envoyant l'identifiant du tableau message
    • L1 : réception dans t qui pointe donc la même zone que message ; ce sont des alias du même contenu.
    • L2 : pn modifie la case 0 de t et donc message référence maintenant le contenu ['Ca', 'au', 'départ'].
    • L3 : pn modifie la case 1 de t et donc message référence maintenant le contenu ['Ca', 'Alors', 'départ'].
    • L4 : pn modifie la case 2 de t et donc message référence maintenant le contenu ['Ca', 'Alors', '!'].
    • (L4) : pas de retour visible mais on renvoie donc None.
    • L8 (réception de ce None)
    • L9 : affiche ['Ca', 'Alors', '!'] et fin du programme.

    14° Observer le code suivant sur Python Tutor. Donner le contenu du tableau notes et du tableau coefs 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] coefs = [5, 4, 3] modifier(coefs, 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.

    Python Tutor montre bien comment les arguments sont stockés dans les paramètres

    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.

    Python Tutor montre bien comment le paramètre permet de modifier l'état du tableau

    15° On considère le programme suivant :

    1 2 3 4 5 6
    def mystere(t:list) -> None: for i in range( len(t) ): t[i] = 10 tglob = [12, 15, 8] mystere(tglob)
    1. tglob et t sont-ils des alias ou des copies l'un de l'autre au vu des lignes 5(appel) et 1(déclaration) ?
    2. Expliquer clairement ce que fait cette fonction sur le tableau tglob qu'on lui passe en argument.

    ...CORRECTION...

    1. On transmet l'argument tglob qui sera stocké via le paramètre t. Les deux tableaux ont donc le même identifiant et pointent la même zone mémoire. Ce sont des ALIAS.
    2. Pour chaque indice du tableau t, on double le CONTENU de cette case. Puisque les deux tableaux pointent le même contenu, on va donc multiplier par deux les valeurs contenues dans le tableau tglob.

    16° Compléter la fonction multiplication() qui modifie le tableau par effet de bord en multipliant les éléments de t par le second paramètre coef : si une case contient 5 et que le coefficient est 10, la case devra contenir 50.

    1 2 3 4 5 6 7
    def multiplication(t:list, coef:int) -> None: for i in ...: t[i] = ... tglob = [12, 15, 8] multiplication(tglob, 10) print(tglob)

    ...CORRECTION...

    1 2 3 4 5 6 7
    def multiplication(t:list, coef:int) -> None: for i in range( len(t) ): t[i] = t[i] * coef tglob = [12, 15, 8] multiplication(tglob, 10) print(tglob)
    3.3 Tableaux : copie et alias

    A - Définition : alias

    Des alias sont des variables qui pointent exactement vers le même contenu-mémoire.

    Si on modifie le contenu pointé en utilisant l'un des alias, l'utilisation de l'autre alias va également pointer vers le contenu modifié.

    Pour réaliser des alias en Python, il suffit de réaliser une affectation : les deux variables vont pointer vers le même contenu mutable.

    >>> t1 = [10, 20, 30] >>> t2 = t1 # t2 et t1 référencent le même contenu-mémoire >>> id(t1) 139936748427648 >>> id(t2) 139936748427648 >>> t1[0] = 1000 # On modifie t1 >>> t1 [1000, 20, 30] >>> t2 # t2 est modifié aussi [1000, 20, 30]
    B - Définition : copie

    Une copie pointe vers un contenu-mémoire qui a exactement le même contenu qu'une autre zone-mémoire.

    Si on modifie le contenu de la copie, on ne modifie pas le contenu initial.

    Si on modifie le contenu initial après copie, on ne modifie pas la copie.

    Pour réaliser des copies de tableaux contenant des types simples en Python, il suffit de réaliser une création par compréhension.

    >>> t1 = [10, 20, 30] >>> t2 = [v for v in t1] # contenu similaire à t1 mais ailleurs >>> id(t1) 139936748427648 >>> id(t2) 139936751349056 >>> t1[0] = 1000 # On modifie t1 >>> t1 [1000, 20, 30] >>> t2 # t2 n'est PAS modifié [10, 20, 30]
    C - Visuel de la différence alias et copie
    Copie et alias

    t1 et t2 sont des alias puisqu'elles référencent la même zone mémoire.

    t3 est une copie du contenu précédent : valeurs identiques mais dans une zone mémoire différente.

    17° Modifier la fonction multiplication() précédente : elle doit maintenant renvoyer une copie du tableau mais dans lequel les éléments sont multipliés par coef.

    1 2 3 4 5 6 7 8 9 10
    def multiplication(t:list, coef:int) -> list: copie = [v for ... in ...] for i in ...: copie[i] = ... return ... tglob = [12, 15, 8] t2 = multiplication(tglob, 10) print(tglob) print(t2)

    ...CORRECTION...

    1 2 3 4 5 6 7 8 9 10
    def multiplication(t:list, coef:int) -> list: copie = [v for v in t] for i in range( len(copie) ): copie[i] = copie[i] * coef return copie tglob = [12, 15, 8] t2 = multiplication(tglob, 10) print(tglob) print(t2)

    18° Modifier la fonction multiplication() précédente : elle ne doit plus contenir qu'une seule ligne : on renvoie directement le tableau créé par compréhension.

    1 2 3 4 5 6 7
    def multiplication(t:list, coef:int) -> list: return [... for ... in ...] tglob = [12, 15, 8] t2 = multiplication(tglob, 10) print(tglob) print(t2)

    ...CORRECTION...

    1 2 3 4 5 6 7
    def multiplication(t:list, coef:int) -> list: return [v*coef for v in t] tglob = [12, 15, 8] t2 = multiplication(tglob, 10) print(tglob) print(t2)

    4 - Bilan fonction et type constuit mutable

    4.1 Fonction : paramètre référençant un mutable

    Principe : espace de noms

    Une variable Python n'est pas une boîte contenant un contenu.

    1. En Python, l'espace des noms permet de comprendre la variable comme une traduction entre un nom et une référence-mémoire (dont on peut connaître l'identifiant avec id()).
    2. En particulier, une variable référençant un type construit symbolise l'adresse du conteneur et pas les valeurs contenues.
    3. Copie et alias
    4. En conséquence, si on transmet à une fonction un argument de type construit et mutable, le paramètre correspondant va mener à la même zone mémoire que l'argument : l'argument et le paramètre sont des alias du même conteneur.
    5. Conclusion : modifier le contenu du conteneur en utilisant le paramètre (local) va donc également modifier le contenu de l'argument envoyé.
    4.2 Fonction : lire un tableau

    Pour lire le contenu d'un tableau depuis une fonction : deux méthodes possibles

    • L'accès par indices
    • 1 2 3 4 5 6
      def afficher(t): for i in range( len(t) ): print(t[i]) notes = [15, 18, 8, 10, 12, 15, 20, 5, 12, 17, 12, 10, 18, 4] afficher(notes)
    • L'accès par valeurs
    • 1 2 3 4 5 6
      def afficher(t): for v in t: print(v) notes = [15, 18, 8, 10, 12, 15, 20, 5, 12, 17, 12, 10, 18, 4] afficher(notes)
    4.3 Fonction : modifier un tableau

    Pour modifier un tableau depuis une fonction : il faut passer par les indices.

    1 2 3 4 5 6
    def modifier(t): for i in range( len(t) ): t[i] = t[i] ** 2 notes = [15, 18, 8, 10, 12, 15, 20, 5, 12, 17, 12, 10, 18, 4] modifier(notes)

    La modification est possible par effet de bord : le paramètre reçoit en réalité l'identifiant du tableau initial et devient ainsi un alias du tableau initial.

    4.4 Fonction : renvoyer une copie d'un tableau

    Pour renvoyer un nouveau tableau basé sur un tableau-paramètre depuis une fonction : une seule méthode utilisable

    • Créer une copie du tableau initial
    • Modifier la copie en utilisant les indices
    • Renvoyer la copie (et la stocker dans une variable !)
    1 2 3 4 5 6 7 8
    def copier_et_modifier_la_copie(t): copie = [v for v in t] for i in range( len(copie) ): copie[i] = copie[i] * 2 return copie notes = [15, 18, 8, 10, 12, 15, 20, 5, 12, 17, 12, 10, 18, 4] nouvelles_notes = copier_et_modifier_la_copie(notes)

    Dans la plupart des cas, la création par compréhension peut résoudre le problème posé en une ligne :

    1 2 3 4 5
    def copier_et_modifier_la_copie(t): return [v*2 for v in t] notes = [15, 18, 8, 10, 12, 15, 20, 5, 12, 17, 12, 10, 18, 4] nouvelles_notes = copier_et_modifier_la_copie(notes)

    5 - FAQ

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