Skip to content

TD12. Redonnons la main

Le sujet de ce TD est disponible au format pdf ici.

L'objectif de ce TD est d'introduire les générateurs Python, c'est à dire le mot-clef yield, et le concept fondamental d'itérateur qui se cache derrière ainsi que le concept d'itérable spécifique à Python.

Exercice 1 : quel est le problème ?

Question 1

Analysez attentivement le code ci-dessous. Combien de lignes du fichier d'entrée faut-il lire pour que le programme affiche la note de l'étudiant situé en toute première position dans le fichier ?

 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
#!/usr/bin/env python3

import sys

def traite_fichier(nom_fichier):
    fichier = open(nom_fichier, "r")
    resultats = []
    for ligne in fichier:
        ligne_decoupee = ligne.split(" ")
        prenom = ligne_decoupee[0]
        note = int(ligne_decoupee[1])
        resultats.append((prenom, note))
    fichier.close()
    return resultats

def cherche_note(prenom, prenoms_notes):
    for resultat in prenoms_notes:
        if resultat[0] == prenom:
            return resultat[1]
    return None

def get_note():
    if len(sys.argv) != 3:
        print("Usage :", sys.argv[0], "nom_fichier prenom")
        return
    prenoms_notes = traite_fichier(sys.argv[1])
    prenom = sys.argv[2]
    print("La note de", prenom, "est", cherche_note(prenom, prenoms_notes))

if __name__ == "__main__":
    get_note()

Question 2

Cette fois, on cherche à calculer la moyenne des notes se trouvant dans le fichier. Que peut-on dire sur l'espace mémoire occupé par ce programme ?

 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
#!/usr/bin/env python3

import sys

def traite_fichier(nom_fichier):
    fichier = open(nom_fichier, "r")
    resultats = []
    for ligne in fichier:
        ligne_decoupee = ligne.split(" ")
        prenom = ligne_decoupee[0]
        note = int(ligne_decoupee[1])
        resultats.append((prenom, note))
    fichier.close()
    return resultats

def calcule_moyenne(prenoms_notes):
    somme = 0
    nb_etudiants = 0
    for resultat in prenoms_notes:
        somme += resultat[1]
        nb_etudiants += 1
    return somme / nb_etudiants

def teste():
    """Teste les fonctions ci-dessus."""
    if len(sys.argv) != 2:
        print("Usage :", sys.argv[0], "nom_fichier")
    else:
        prenoms_notes = traite_fichier(sys.argv[1])
        print("La note moyenne est", calcule_moyenne(prenoms_notes))

if __name__ == "__main__":
    teste()

Question 3

Comment corriger le problème de calculs inutiles et de mémoire dans le cas de la recherche d'un étudiant uniquement avec ce que nous avons vu jusqu'ici, c'est-à-dire sans avoir recours à yield ?

Question 4

Quel est l'inconvénient de cette solution ?

Exercice 2 : il n'y a pas de problème, il n'y a que des solutions

Voici donc comment, à l'aide de l'utilisation d'un yield Python, avoir une recherche d'étudiant dans laquelle :

  • nous ne faisons aucun calcul pour rien ;
  • nous ne créons pas de list d'étudiants, donc utilisation mémoire constante ;
  • le traitement du fichier et la recherche sont séparés dans deux fonctions.
 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
#!/usr/bin/env python3

import sys

def traite_fichier(fichier):
    for ligne in fichier:                                      # tf1 (bof, non ?)
        ligne_decoupee = ligne.split(" ")                      # tf2
        prenom = ligne_decoupee[0]                             # tf3
        note = int(ligne_decoupee[1])                          # tf4
        yield (prenom, note)                                   # tf5

def cherche_note(prenom, resultats):
    for resultat in resultats:                                 # cn1
        if resultat[0] == prenom:                              # cn2
            return resultat[1]                                 # cn3
    return None                                                # cn4

def get_note():
    if len(sys.argv) != 3:                                     # gn1
        print("Usage :", sys.argv[0], "nom_fichier prenom")    # gn2
        return                                                 # gn3
    fichier = open(sys.argv[1], "r")                           # gn4
    iterateur_prenoms_notes = traite_fichier(fichier)          # gn5
    prenom = sys.argv[2]                                       # gn6
    note = cherche_note(prenom, iterateur_prenoms_notes)       # gn7
    print("La note de", prenom, "est", note)                   # gn8
    fichier.close()                                            # gn9

if __name__ == "__main__":                                     # 1
    get_note()                                                 # 2

Question 1

Analyser attentivement le code ci-dessus. En essayant de "deviner" ce que fait le yield, prendre quelques minutes pour dérouler le programme sur papier en écrivant les numéros de lignes successivement exécutées dans le cas d'un appel correct, c'est à dire avec un fichier qui existe et un nom d'étudiant qui existe en troisième position du fichier.

Question 2

À l'aide d'un générateur, réécrire le programme qui calcule la moyenne des notes. Quel est l'avantage par rapport au calcul de moyenne de l'exercice 1 ?

Exercice 3 : générer les jours de la semaine.

Question 1

Écrire une fonction génératrice renvoyant un itérateur sur les chaînes de caractères représentant les jours de la semaine.

Question 2

Qu'affiche le programme suivant ?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/env python3

"""Petit exemple pour illustrer le contexte de CHAQUE itérateur 1 et 2"""

def get_first_five():
    """Fonction génératrice des 5 premiers nombres."""
    yield "one"
    yield "two"
    yield "three"
    yield "four"
    yield "five"

def affiche():
    """Affiche des trucs en utilisant DEUX itérateurs."""
    iterateur_1 = get_first_five()
    iterateur_2 = get_first_five()

    print("De l'itérateur 1", next(iterateur_1))
    print("De l'itérateur 1", next(iterateur_1))
    print("De l'itérateur 2", next(iterateur_2))
    print("De l'itérateur 1", next(iterateur_1))

if __name__ == "__main__":
    affiche()

Exercice 4 : analyse de code

Question 1

Qu'affiche le programme ci-dessous ?

 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
#!/usr/bin/env python3

"""Train reading someone else (bad) code"""


def mystery_function(first_parameter, second_parameter, everything):
    """What am I doing?"""
    for variable in first_parameter:
        everything.append(variable[second_parameter])
        yield variable[second_parameter]


def main():
    """Script's entry point"""
    a_list = []
    something = mystery_function([("to", 12), ("ti", 17), ("ta", 47)], 1, a_list)
    print("type of something is", type(something))
    for element in something:
        print(element, end=" ")
    print()
    something_else = mystery_function(("to", "ti", "\u0634 what is that?"), 0, a_list)
    print("type of something_else is", type(something_else))
    for element in something_else:
        print(element, end=" ")
    print()
    print(a_list)
    other_thing = mystery_function({(1, 2, 3), (4, 5, 6), (7, 8)}, 2, a_list)
    print("type of other_thing is", type(other_thing))
    for element in other_thing:
        print(element, end=" ")
    print()


if __name__ == "__main__":
    main()

Exercice 5 : quand aurions nous pu/dû utiliser yield ? (pour aller plus loin)

Question 1

Chercher dans ses notes, sa mémoire, son ordinateur, à quels endroits nous aurions pu/dû utiliser yield dans le cadre des TD et TP BPI en justifiant pourquoi ?