Skip to article frontmatterSkip to article content

Licence CC BY-NC-ND, Thierry Parmentelat & Laurent Lacourt

NOTES IMPORTANTES :

class Student:
    ...

    def add_grade(self, topic: str, grade: float):
        # Votre code ici
        pass

    ...

Notez l’apparition du self comme premier argument ! Bon courage ! ;)

La classe Student

Nous allons commencer par créer une classe Student qui va nous permettre d’instancier des étudiants. Le noyau de la classe est le suivant :

from collections import defaultdict

class Student:
    def __init__(self, first_name: str, last_name: str):
        pass

    def __repr__(self):
        pass

Dans la suite du TP, vous allez devoir ajouter des fonctionnalités à la classe Student. Pour ce faire, vous devez revenir à chaque fois à cette cellule, implémenter votre code et ré-évaluer la cellule pour que les changements soient pris en compte.

Aussi on vous receommande de “faire descendre” cette cellule au fur et à mesure que vous ajoutez les fonctionnalités et que vous passez le test correspondant, de sorte d’avoir toujours la cellule de code juste au dessus de la cellule de test; on peut utiliser pour cela les touches de raccourci (d) ou le bouton

Constructeur et __repr__

Surchargez les deux méthodes __init__ et __repr__ dans la cellule ci-dessus. (Et n’oubliez pas de la ré-évaluer!)

try:
    student = Student("Achille", "Talon")
    if repr(student) != "Achille Talon":
        raise Exception("There is an issue in your __repr__ method.")
except Exception as e:
    print('OOPS - There is an issue in your code.')
    print(f"Error message : {e}")
else:
    print('Congrats ! Your implementation works !')
OOPS - There is an issue in your code.
Error message : __repr__ returned non-string (type NoneType)

Gestion des notes

add_grade

Nous souhaitons maintenant ajouter des notes à cet élève. Implémentez la méthode add_grade(topic: str; grade: float) dans la classe Student pour que le code suivant s’exécute sans erreur :

Note : Pour stocker les notes de l’élève vous allez devoir ajouter un attribut à la classe Student. Prenez le temps de réfléchir au conteneur le plus approprié.

try:
    student = Student("Achille", "Talon")
    student.add_grade("History", 10.)
    student.add_grade("History", 12.)
except Exception as e:
    print('OOPS - There is an issue in your add_grade method.')
    print(f"Error message : {e}")
else:
    print('Congrats ! Your implementation works !')
OOPS - There is an issue in your add_grade method.
Error message : 'Student' object has no attribute 'add_grade'

followed_topics

Maintenant, nous aimerions savoir quelles matières suit un élève via la méthode followed_topics() qui renvoyer un itérable.

try:
    student = Student("Achille", "Talon")
    student.add_grade("History", 10.)
    topics = student.followed_topics()
    if len(topics) != 1 or "History" not in topics:
        raise Exception(f"Expecting ['History'] got {topics}")
except Exception as e:
    print('OOPS - There is an issue in your followed_topics method')
    print(f"Error message : {e}")
else:
    print('Congrats ! Your implementation works !')
OOPS - There is an issue in your followed_topics method
Error message : 'Student' object has no attribute 'add_grade'

compute_average

Nous voudrions calculer la moyenne de l’élève pour une matière donnée. Implémentez la méthode compute_average(topic: str). Nous fixons comme convention que si l’élève n’a pas de note dans la matière demandée, la méthode renvoie -1 (n’oubliez pas la méthode que vous venez de coder...). Le code suivant doit s’exécuter sans erreur :

try:
    student = Student("Achille", "Talon")
    student.add_grade("History", 10.)
    student.add_grade("History", 12.)
    if (student.compute_average("History") != 11.):
        raise Exception("Issue in your average calculation.")
    if (student.compute_average("French") != -1.):
        raise Exception("If topic is not followed return -1")
except Exception as e:
    print('OOPS - There is an issue in your compute_average method.')
    print(f"Error message : {e}")
else:
    print('Congrats ! Your implementation works !')
OOPS - There is an issue in your compute_average method.
Error message : 'Student' object has no attribute 'add_grade'

report

Finalement, il ne manque plus qu’à afficher à l’écran le bulletin de l’élève en codant la fonctionreport() qui renvoie la chaine de caractères qui s’affiche comme suit :

Report for student Albert Einstein
+===============+===============+
|     Topic     |    Average    |
+===============+===============+
|   Chemistry   |     11.33     |
+---------------+---------------+
|    English    |     14.00     |
+---------------+---------------+
|    Physics    |     13.25     |
+---------------+---------------+
|    French     |     11.50     |
+---------------+---------------+
|  Mathematics  |     12.80     |
+---------------+---------------+
|  Scubadiving  |     12.50     |
+---------------+---------------+
try:
    reference_lines = ['Report for student Albert Einstein',
                       '+===============+===============+',
                       '|     Topic     |    Average    |',
                       '+===============+===============+',
                       '|  Mathematics  |     12.80     |',
                       '+---------------+---------------+',
                       '|  Scubadiving  |     12.50     |',
                       '+---------------+---------------+']

    student = Student("Albert", "Einstein")
    student.add_grade("Mathematics", 12.80)
    student.add_grade("Scubadiving", 12.50)
    report = student.report()
    report_lines = report.strip().split('\n')
    for i, (lineref, linestudent) in enumerate(zip(reference_lines, report_lines), start=1):
        assert lineref == linestudent, f"Ligne {i} : attendu = {lineref}// obtenu = {linestudent}"
except AssertionError as e:
    print("Les deux chaines sont différentes")
    print(e)
except Exception as e:
    print("OOPS - Something's wrong")
    print(f"Error message : {e}")
else:
    print('Congrats ! Your implementation works !')
OOPS - Something's wrong
Error message : 'Student' object has no attribute 'add_grade'

La classe Class

Nous allons maintenant gérer un ensemble d’élèves dans l’objet Class.

class Class:
    def __init__(self, classname: str):
        pass

    def add_student(self, student: Student):
        pass

    def __len__(self):
        return 0

    def __repr__(self):
        return f""

Premières méthodes

Implémentez les méthodes dont les prototypes sont données dans la cellule précédente. Le code suivant doit s’exécuter sans problèmes :

Note : Nous aurons besoin par la suite de savoir rapidement si un élève est dans la classe et d’y accéder. Évitez donc de stocker les étudiants dans une simple liste.

try:
    classe = Class("P20")
    student = Student("Matthieu", "Mazière")
    classe.add_student(student)
    if len(classe) != 1:
        raise Exception('OOPS - There is an issue in your __len__ method.')
    if repr(classe) != "Class P20 - 1 student(s)":
        raise Exception('OOPS - There is an issue in your __repr__ method.')
except Exception as e:
    print("OOPS - Something's wrong")
    print(f"Error message : {e}")
else:
    print('Congrats ! Your implementation works !')
OOPS - Something's wrong
Error message : OOPS - There is an issue in your __len__ method.

Accès à un élève

Nous aimerions pouvoir accèder très facilement à un élève de la classe. Pour cela, codez la méthode get_student(first_name: str, last_name:str) qui permet au code suivant de s’exécuter sans erreur :

try:
    classe = Class("P20")
    student = Student("Matthieu", "Mazière")
    classe.add_student(student)
    new_student = classe.get_student("Matthieu", "Mazière")
    assert student == new_student
    new_student = classe.get_student("Jérôme", "Adnot")
    assert new_student is None
except Exception as e:
    print("OOPS - Something's wrong")
    print(f"Error message : {e}")
else:
    print('Congrats ! Your implementation works !')
OOPS - Something's wrong
Error message : 'Class' object has no attribute 'get_student'

Initialisons la classe à partir d’un fichier

Nous nous plaçons dans le cas où l’effectif d’une classe est définie dans le fichier classe.csv. Chaque ligne de ce fichier contient le prénom et le nom d’un étudiant (vous en connaissez peut-être quelques-uns) :

!head -10 class.csv
Andre Marie,Ampère
Henri,Becquerel
Daniel,Bernoulli
Alfred,Binet
Niels,Bohr
Ludwig,Boltzmann
Max,Born
Carl,Bosch
Lawrence,Bragg
Louis,de Broglie

Implémentez la méthode load_students_from_file(filename: str) qui permet de remplir la classe. Le code suivant doit s’exécuter sans problèmes :

try:
    classe = Class("P1920")
    classe.load_students_from_file('class.csv')
    if len(classe) != 90:
        raise Exception('OOPS - There is an issue in your load_from_file method')
except Exception as e:
    print("OOPS - Something's wrong")
    print(f"Error message : {e}")
else:
    print('Congrats ! Your implementation works ! ')
OOPS - Something's wrong
Error message : 'Class' object has no attribute 'load_students_from_file'

Saisie de notes pour les élèves

Nous allons maintenant rentrer les notes des élèves pour les différentes matières. Cette saisie ce fait aussi via un fichier csv que vous allez devoir parser. Dans ce cas, chaque ligne du fichier est découpée comme suit :

Prénom, Nom, Matière, Note1, Note2, ..., NoteN

!head -5 grades.csv
Francis,Galton,Physics,17.0,15.0,8.0,8.0
Enrico,Fermi,English,17.0,14.0
Heinrich,Hertz,Mathematics,17.0,12.0,13.0,11.0,18.0
Henri,Poincaré,Mathematics,11.0,11.0,13.0,11.0,10.0
Claude,Levi-Strauss,Physics,15.0,12.0,14.0,12.0

Il est à noter d’une part que le nombre de notes dépend de la matière et d’autre part que tous les élèves ne suivent pas les mêmes cours. Implémentez la méthode load_grades_from_file(filename: str) qui permet d’affecter à chaque étudiant ses notes.

Note : Dans le cas où l’on souhaiterait attribuer une note à un étudiant qui n’est pas dans la classe, il ne faut pas lever d’exception, mais seulement imprimer un message d’avertissement.

try:
    classe = Class("P1920")
    classe.load_students_from_file('class.csv')
    classe.load_grades_from_file('grades.csv')
    assert classe.get_student("Albert", "Einstein").compute_average("Physics") == 13.25
    assert classe.get_student("Richard", "Feynman").compute_average("Physics") == 12.
    assert classe.get_student("Pierre", "Curie").compute_average("Scubadiving") == 9.5
except Exception as e:
    print("OOPS - Something's wrong")
    print(f"Error message : {e}")
else:
    print('Congrats ! Your implementation works ! ')
OOPS - Something's wrong
Error message : 'Class' object has no attribute 'load_students_from_file'

Catalogue des matières suivies par les élèves de la classe

Implémentez la méthode catalog() qui renvoie un dictionnaire dont les clés sont les noms des matières et les valeurs sont le nombre d’étudiants suivant chaque cours. Le code suivant vous permet de valider votre implémentation :

try:
    classe = Class("P1920")
    classe.load_students_from_file('class.csv')
    classe.load_grades_from_file('grades.csv')
    true_catalog = {'Physics': 90, 'Mathematics': 90, 'Chemistry': 90,
                    'English': 70, 'French': 30, 'Scubadiving': 10, 'Horse-riding': 15, 'Sailing': 3}
    assert classe.catalog() == true_catalog
except Exception as e:
    print("OOPS - Something's wrong")
    print(f"Error message : {e}")
else:
    print('Congrats ! Your implementation works ! ')
OOPS - Something's wrong
Error message : 'Class' object has no attribute 'load_students_from_file'

Calcul des moyennes par matière

Nous allons maintenant nous intéresser à calculer des moyennes de la classe par matière. Codez la méthode compute_averages(). Cette dernière doit retourner un dictionnaire dont les clés sont des matières et les valeurs les moyennes de la classe.

try:
    import math
    classe = Class("P1920")
    classe.load_students_from_file('class.csv')
    classe.load_grades_from_file('grades.csv')
    true_averages = {'Physics': 12.036111111111111,
                     'Mathematics': 12.082222222222223,
                     'Chemistry': 11.996296296296292,
                     'English': 12.414285714285715,
                     'French': 11.683333333333334,
                     'Scubadiving': 11.7,
                     'Horse-riding': 11.366666666666667,
                     'Sailing': 14.0}
    avgs = classe.compute_averages()
    assert all(math.isclose(avgs[top], true_averages[top]) for top in true_averages)
except Exception as e:
    print("OOPS - Something's wrong")
    print(f"Error message : {e}")
else:
    print('Congrats ! Your implementation works ! ')
OOPS - Something's wrong
Error message : 'Class' object has no attribute 'load_students_from_file'

Pour les plus forts

Répercutez les infos sur la classe sur la méthode report de l’étudiant. Vous pouvez par exemple afficher la moyenne de l’étudiant mais aussi celle de la classe, le nombre d’élèves suivant le cours ou le rang de l’élève dans la classe.