<string>

<string>

Table des matières

Mission 8 - Classes et objets

Mission 8 : Classes et objets

Mission 8 : Classes et objets

Introduction

L'informatique et l'internet ont révolutionné le monde de la diffusion musicale. Grâce à la numérisation et aux connexions à haut débit, chacun peut de nos jours stocker et échanger très facilement de larges volumes de musique. La vente en ligne a détrôné les magasins physiques, les ventes de disques sont en chute libre et les logiciels sont désormais au centre de la diffusion musicale, que ce soit dans les serveurs des diffuseurs, sur nos ordinateurs et sur les baladeurs et smartphones dans nos poches.

Spotify, un service logiciel d'écoute musicale bien connu.

Fondé par de jeunes diplômés de l'UCL, la société SoundPlaza compte mettre en place un espace de rencontre pour les musiciens de la région, avec une forte présence sur la Toile. Ils veulent offrir un site de diffusion de musique pour leurs membres et font appel à votre service pour les aider à développer leur infrastructure de gestion de données. Ils désirent, à partir de la liste des chansons disponibles sur leur serveur, assembler automatiquement des albums correspondant à la taille d'un CD. Ils comptent sur vos compétences nouvellement acquises en programmation orientée objets pour définir et exploiter les structures de données nécessaires.

Depuis le début de ce cours, vous avez commencé à manipuler des classes et objets déjà définis (comme les classes str, int, float et Turtle, par exemple). Néanmoins, cette mission marque votre entrée dans le vrai monde de la programmation orientée objets. Cette semaine, vous allez définir vos propres classes et objets.

Objectifs

A l'issue de cette mission, chacun d'entre vous :

  • pourra expliquer en ses propre mots en quoi consiste la programmation orientée objets;
  • pourra expliquer et illustrer les notions élémentaires de la programmation orientée objets telles que:
    • classe, objet et instance;
    • attributs et méthodes d'instance;
    • constructeur et méthode d'initialisation d'une classe;
  • pourra faire la distinction entre:
    • la référence à un objet et l'objet lui-même;
    • l'égalité superficielle et profonde entre deux objets;
    • une copie superficielle et profonde d'un objet.
  • aura approfondi sa connaissance des facilités de manipulation de texte en Python, et en particulier la conversion automatique par la méthode spéciale __str__;
  • aura écrit un premier programme utilisant des objets en Python;
  • aura réalisé quelques tests de classes simples.

Préparation, étude et apprentissage

La matière relative à cette mission est décrite dans les sections suivantes de la partie Objects du syllabus en ligne:

Questionnaire de démarrage

Questions à choix multiple

Les questions à choix multiples de cette mission sont accessibles en ligne depuis https://inginious.info.ucl.ac.be/course/LSINF1101-PYTHON/Session9_QCM


        
        

Questions ouvertes

Définitions

Considérez le code suivant d'une classe Student représentant un étudiant:

class Student :

    def __init__(self,n) :
        """
        Initialise un nouvel objet de type Student avec un nom n donné.
        @pre:  -
        @post: Un objet de type Student a été créé avec comme 'name' n,
               et un score None pour chacun des trois tests 'test1', 'test2' et 'test3'
        """
        # nom de l'étudiant
        self.name = n
        # score reçu par l'étudiant sur trois tests
        # (initialement None car l'étudiant n'a pas encore passé les tests)
        self.test1 = None
        self.test2 = None
        self.test3 = None

    def average_score(self) :
        """"
        Calcul du score moyen que l'étudiant a obtenu sur les 3 tests.
        @pre: les variables d'instance test1, test2 et test3
              contiennent des valeurs de type int
        @post: retourne la valeur moyenne de ces trois valeurs
        """
        return (self.test1 + self.test2 + self.test3) / 3

Les instructions suivant donnent un exemple de comment cette classe peut être utilisé:

student = Student("Kim")
student.test1 = 14.0
student.test2 = 10.5
student.test3 = 12.0
print("Bonjour, " + student.name + ". Vos scores sont:")
print(student.test1)
print(student.test2)
print(student.test3)
print("Votre score moyenne est " + str(student.average_score()))
student2 = student
print("Votre score moyenne est " + str(student2.average_score()))
student = None
student2 = None

Sur base de ces extraits, donnez des exemples des notions suivantes, en veillant à être précis et rigoureux dans votre formulation:

**Classe, objet (ou instance)**
 
 
 
 
 
 
 
**Constructeur et méthode d'initialisation**
 
 
 
 
 
 
**Attribut (ou variable d'instance)**
 
 
 
 
 
**Méthodes d'instance**
 
 
 
 
**Référence à un objet et la valeur None**
 
 
 
 
**Référence à self**
 
 
 
 

Classes et objets

Dans la question précédente, expliquez les notations student.name, student.average_score() et self.name et leur interprétation par Python.

 
 
 
 
 
 
 
 

Méthode de conversion de string

Dans la première question, pour imprimer le détail d'un objet student de la classe Student on devait exécuter la série d'instructions suivante:

print("Bonjour, " + student.name + ". Vos scores sont:")
print(student.test1)
print(student.test2)
print(student.test3)
print("Votre score moyenne est " + str(student.average_score()))

Au lieu d'exécuter cette série d'instructions à l'extérieur de la classe, ce serait beaucoup plus facile de pouvoir obtenir le même résultat en écrivant seulement:

print(student)

A cette fin, ajoutez une méthode magique __str__ à la classe Student qui retourne un string du style:

"Bonjour, Kim. Vos scores sont:
 14.0
 10.5
 12.0
 Votre score moyenne est 12.166666666666666"

Tester votre code comme suite:

>>> student = Student("Kim")
>>> student.test1 = 14.0
>>> student.test2 = 10.5
>>> student.test3 = 12.0
>>> print(student)
Bonjour, Kim. Vos scores sont:
14.0
10.5
12.0
Votre score moyenne est 12.666666666

Une classe simple

Dans le cadre d'un cours de programmation, un étudiant a développé une classe Pair permettant de manipuler un objet contenant une paire d'entiers. Le code de cette classe Python est repris ci-dessous :

class Pair:
    """
    Une paire d'entiers
    """

    def __init__(self, x=None, y=None):
        """
        @pre -
        @post crée une paire (a,b) composée de x et y,
              ou une paire non-initialisée si aucune valeur
              de x et de y n'est donné lors de l'appel au constructeur
        """
        self.a = x   # le premier élément de la paire
        self.b = y   # le second élément de la paire

    def __str__(self):
        return str(self.a) + ", " + str(self.b)

Considérons le code Python ci-dessous illustrant une utilisation de la classe Pair:

p0 = Pair()      ##1##
p1 = Pair(0,0)   ##2##
p2 = Pair(1,1)   ##3##
p0.b = 3         ##4##
p1.a = 10        ##5##
p2.a = p1.a      ##6##
p2 = p1          ##7##

Expliquez les opérations effectuées par chacune de ces lignes. Pensez à utiliser une représentation graphique des différents objets telle que celle utilisée dans le syllabus ou les transparents en indiquant les valeurs des différentes variables d'instance.

 
 
 
 
 
 
 
 
 
 
 
 
 
 

Après avoir exécuté les opérations précédentes, qu'impriment les instructions suivantes?

print(p0)        ##7##
print(p1)        ##8##
print(p2)        ##9##
 
 
 

Création de classes

Dans cet exercice, il vous est demandé de créer une classe Student.

Les étudiants sont supposés avoir les caractéristiques suivantes:

  • Un prénom (attribut name)
  • Un nom (attribut surname)
  • Une date de naissance (attribut birth_date)
  • Une adresse mail (attribut email)

Nous créerons les instances de Student avec l'instruction suivante:

stu = Student('firstname', 'surname', 'birthday', 'email')


        
        

Une méthode simple

Considérons à nouveau la classe Pair. Dans cette classe, comment feriez-vous pour écrire une méthode opposite:

def opposite(self):
    """
    @pre        -
    @post   retourne une nouvelle instance de Pair dont les deux
            éléments sont les opposés de ceux de cette paire-ci.
            L'instance appelante reste inchangée.
    """
    # À implémenter

Cette méthode renvoie une nouvelle instance de la classe Pair telle que ses variables d'instance, a et b, valent l'opposé des variables a et b de l'instance qui y fait appel. Voici un exemple de l'utilisation de cette méthode :

p = Pair(10, -2)
print(p)            # affiche "10, -2"
p.a = 12
print(p)            # affiche "12, -2"
q = p.opposite()
print(p)            # affiche "12, -2"
print(q)            # affiche "-12, 2"


        
        

Egalité

Considérez les instructions suivantes qui utilisent la classe Pair vue précédemment:

p1 = Pair(9, 42)
p2 = Pair(9, 42);
if (p1 == p2) : print("Egaux en 1")   ##1##
p2 = p1
if (p1 == p2) : print("Egaux en 2")   ##2##

Lesquelles des lignes ##1## et/ou ##2## produiront-elles un message?

 
 
 
 

Ajoutez une méthode __eq__(self, p) à la classe Pair qui compare la valeur de deux paires, de manière à ce que p1 == p2 retournera True à la ligne ##1##. Pensez à gérer également le cas où p == None.


        
        

OrderedPair

Un étudiant propose ensuite de construire une classe OrderedPair qui utilise la classe Pair définie précédemment et y ajoute un booléen. Ce booléen est utilisé pour indiquer si une instance de la classe OrderedPair est ordonnée ou non: il est vrai lorsque la valeur stockée dans l'attribut a est inférieure ou égale à la valeur stockée dans l'attribut b.

Considérez la classe OrderedPair, qui prend comme variable d'instance un objet de type Pair dont la classe se trouve dans la question précédente. Voici la classe OrderedPair :

class OrderedPair:

    def __init__(self):
        self.p = Pair(0, 0)
        self.ordered = True  # vrai si les deux entiers sont ordonnés: p.a <= p.b

    def set_a(self, n_a):
        """
        @pre -
        @post donne au premier élément de la paire la valeur n_a
        """
        # À implémenter

    def set_b(self, n_b):
        """
        @pre -
        @post donne au second élément de la paire la valeur n_b
        """
        # À implémenter

Le but de l'exercice est d'implémenter les méthodes set_a() et set_b(), sans oublier de mettre à jour ordered en fonction des nouvelles valeurs des nombres dans la paire.


        
        

Mission 8

Mission 8

L'objectif principal de cette mission est de définir et d'utiliser trois classes correspondant respectivement à un temps ou une durée (sous la forme heures-minutes-secondes), à une chanson et à un album. Vous utiliserez ensuite ces classes pour réaliser la génération d'albums souhaitée. Vous travaillerez par groupes de deux.

Dans le cadre du prototype à réaliser, les chansons vous sont fournies dans un fichier contenant des lignes de texte:

"TITRE AUTEUR MIN SEC"

où "TITRE", "AUTEUR", "MIN" et "SEC" correspondent respectivement au titre, à l'auteur, au nombre de minutes et de secondes de la chanson, et ne contiennent eux-mêmes pas d'espaces, par exemple:

Let's_Dance David_Bowie 4 5
Relax Frankie_Goes_To_Hollywood 3 54
Purple_Rain Prince 5 48
Enjoy_The_Silence Depeche_Mode 4 13

Ce format facilite leur lecture. On vous rappelle que la méthode split() permet de séparer un string contenant des espaces en mots individuels.

Il est plus que jamais important de bien tester votre code, étant donné que les classes que vous définissez pourront être utilisées dans des usages multiples. Au sein de votre binôme, un étudiant pourra se concentrer sur le développement d'une classe tandis que l'autre préparera des tests. Les spécifications des méthodes étant donnée par avance, il est tout à fait possible de préparer des tests en même temps que (voire avant) la classe à tester. Il s'agit en quelque sorte d'un jeu: le développeur parviendra-t-il à écrire une classe qui fonctionnera correctement sous toutes les attaques du testeur? Le testeur parviendra-t-il à trouver le test qui causera un fonctionnement incorrect? Pour que ces questions aient un sens, il est également primordial de bien spécifier les méthodes.

(Remarque: lors de cette mission on n'utilisera pas encore les 'tests unitaires'; vous pouvez tester votre code comme vous l'avez fait lors des missions précédentes. Les tests unitaires seront introduites dans une mission futur.)

Etapes

  1. Chargez le fichier "music-db.txt". Ce fichier contient une liste de plus de 300 chansons, au format décrit ci-dessus.

  2. Créez une nouvelle classe Duree qui représente une durée exprimée en heures (h), minutes (m) et secondes (s).

  3. Créez une série de tests que vous utiliserez pour tester la classe Duree. Ajoutez des tests au fur et à mesure que vous ajouterez des méthodes à la classe Duree. Il est une bonne idée d'avoir un ou plusieurs tests, avec différents scénarios de test, par méthode de la classe Duree. Par exemple, si votre classe Duree a une méthode ajouter(), vous pourrez implémenter une fonction test_ajouter() pour tester les différents scénarios d'utilisation de la méthode ajouter() de la classe Duree.

  4. Dans la classe Duree, définissez une méthode d'initialisation avec comme paramètres __init__(self, h, m, s), qui requiert que m et s soient dans l'intervalle [0..60[ et crée un nouveau temps de h heures, m minutes et s secondes. Veillez à la spécification et vérifiez que les conditions requises soient respectées.

  5. Dans la classe Duree, ajoutez des méthodes correspondant aux spécifications suivantes:

    def toSecondes(self) :
        """
        Retourne le nombre total de secondes de cette instance de Duree (self).
        """
    
    def delta(self,d) :
        """
        Retourne la différence entre cette instance de Duree (self) et la Duree d passée en paramètre,
        en secondes (positif si ce temps-ci est plus grand).
        """
    
    def apres(self,d):
        """
        Retourne True si cette instance de Duree (self) est plus grand que la Duree d passée en paramètre;
        retourne False sinon.
        """
    
    def ajouter(self,d):
       """
       Ajoute une autre Duree d à cette instance de Duree (self).
       Corrige de manière à ce que les minutes et les secondes soient dans l'intervalle [0..60[,
       en reportant au besoin les valeurs hors limites sur les unités supérieures
       (60 secondes = 1 minute, 60 minutes = 1 heure).
       """
    
    def __str__(self):
        """
        Retourne cette durée sous la forme de texte "heures:minutes:secondes".
        Astuce: la méthode "{:02}:{:02}:{:02}".format(heures, minutes, secondes)
        retourne le String désiré avec les nombres en deux chiffres en ajoutant
        les zéros nécessaires.
        """
    
  6. Ecrivez des fonctions test pour chacune de ces méthodes de la classe Duree, exécutez-les et assurez-vous que les résultats sont conformes aux spécifications, en corrigeant au besoin.

  7. Créez une nouvelle classe Chanson représentant une chanson, caractérisée par un titre t (string), un auteur a (string) et une durée d (Duree). Définissez une méthode d'initialisation avec comme paramètres __init__(self, t, a, d).

  8. Dans la classe Chanson, implémentez une méthode de conversion __str__ correspondant à la description suivante:

    def __str__(self):
        """
        Retourne un String décrivant cette chanson sous le format "TITRE - AUTEUR - DUREE".
        Par exemple: "Let's_Dance - David_Bowie - 00:04:05"
        """
    
  9. Créez une série de tests que vous utiliserez pour tester la classe Chanson.

  10. Créez une classe Album représentant un album contenant une ou plusieurs chansons.

  11. En plus d'une méthode d'initialisation pour créer un album vide, cette classe contient une méthode add(self,chanson) pour ajouter une chanson à un album. Cette méthode retourne False si lors de l'ajout d'une chanson l'album a atteint 100 chansons ou la durée dépasserait 75 minutes. Sinon la chanson est rajoutée et la méthode add retourne True.

  12. Dans la classe Album, implémentez également une méthode de conversion __str__ pour imprimer la description d'un album selon le format suivant:

    Album 21 (12 chansons, 00:47:11)
    01: White_Wedding - Billy_Idol - 00:04:12
    02: Stand_And_Deliver - Adam_&_The_Ants - 00:03:33
    03: You_Spin_Me_Around - Dead_Or_Alive - 00:03:14
    04: Wired_For_Sound - Cliff_Richard - 00:03:38
    05: Some_Like_It_Hot - The_Power_Station - 00:03:45
    06: 99_Luftballons - Nena - 00:03:50
    07: Keep_On_Loving_You - Reo_Speedwagon - 00:03:22
    08: Seven_Into_The_Sea - In_Tua_Nua - 00:03:51
    09: Love_Is_A_Battlefield - Pat_Benatar - 00:05:20
    10: Etienne - Guesch_Patti - 00:04:07
    11: This_Is_Not_A_Love_Song - Public_Image_Limited - 00:04:12
    12: Love_Missile_F1-11 - Sigue_Sigue_Sputnik - 00:04:07
    
  13. Créez une série de tests pour tester la classe Album.

  14. Finalement, complétez votre programme par une série d'instructions qui:

    1. lit dans le fichier "music-db.txt", ligne par ligne, des descriptifs de chanson au format décrit en tête de cette section;
    2. pour chaque ligne lue, construite une instance de Chanson;
    3. stocke ses chansons dans un album;
    4. lorsque le nombre de chansons stockés dans un album a atteint 100 chansons ou la durée dépasserait 75 minutes, imprime à la console un descriptif de cet album avec les chansons accumulées, suivant l'exemple donné ci-dessous;
    5. poursuit la lecture du fichier et l'ajout des chansons dans un album suivant;
    6. répète ces étapes jusqu'à ce que le fichier edt vide et imprime le dernier album.

Si tout va bien votre programme final produira l'output suivant

Album 1 (17 chansons, 01:10:55)
01: Let's_Dance - David_Bowie - 00:04:05
02: Relax - Frankie_Goes_To_Hollywood - 00:03:54
03: Purple_Rain - Prince - 00:05:48
04: Enjoy_The_Silence - Depeche_Mode - 00:04:13
05: Chacun_Fait_C'Qui_Lui_Plait - Chagrin_D'amour - 00:04:08
06: Love_Missile_F1-11 - Sigue_Sigue_Sputnik - 00:04:06
07: Spaceman - Babylon_Zoo - 00:03:59
08: Hijo_De_La_Luna - Mecano - 00:04:19
09: 7_Seconds - Youssou_N'Dour_&_Neneh_Cherry - 00:03:48
10: Osez_Josephine - Alain_Bashung - 00:02:57
11: Dejeuner_En_Paix - Stephan_Eicher - 00:03:27
12: New_Gold_Dream - Simple_Minds - 00:05:31
13: Missing - Everything_But_The_Girl - 00:04:44
14: Nineteen - Paul_Hardcastle - 00:03:38
15: Killer - Adamski - 00:04:13
16: Unbelievable - EMF - 00:03:29
17: Overload - Sugababes - 00:04:36

Album 2 (17 chansons, 01:11:24)
01: Ice_Ice_Baby - Vanilla_Ice - 00:04:29
02: Do_You_Really_Want_To_Hurt_Me - Culture_Club - 00:04:23
03: Under_The_Milky_Way - The_Church - 00:04:57
04: Shout - Tears_For_Fears - 00:06:29
05: Pure_Morning - Placebo - 00:04:15
06: Porcelain - Moby - 00:04:00
07: Toi_Mon_Toit - Elli_Medeiros - 00:03:37
08: Just_A_Friend_Of_Mine - Vaya_Con_Dios - 00:03:22
09: Sleeping_Satellite - Tasmin_Archer - 00:04:36
10: I_Won't_Let_You_Down - DPH - 00:04:05
11: A_Girl_Like_You - The_Smithereens - 00:04:36
12: Ready_To_Go - Republica - 00:03:39
13: Oh_Carolina - Shaggy - 00:03:10
14: La_Sonora - Starflam - 00:03:49
15: Tombe_Pour_La_France - etienne_Daho - 00:04:13
16: The_Captain_Of_My_Heart - Double - 00:03:58
17: Human - Human_League - 00:03:46

...

Album 21 (12 chansons, 00:47:11)
01: White_Wedding - Billy_Idol - 00:04:12
02: Stand_And_Deliver - Adam_&_The_Ants - 00:03:33
03: You_Spin_Me_Around - Dead_Or_Alive - 00:03:14
04: Wired_For_Sound - Cliff_Richard - 00:03:38
05: Some_Like_It_Hot - The_Power_Station - 00:03:45
06: 99_Luftballons - Nena - 00:03:50
07: Keep_On_Loving_You - Reo_Speedwagon - 00:03:22
08: Seven_Into_The_Sea - In_Tua_Nua - 00:03:51
09: Love_Is_A_Battlefield - Pat_Benatar - 00:05:20
10: Etienne - Guesch_Patti - 00:04:07
11: This_Is_Not_A_Love_Song - Public_Image_Limited - 00:04:12
12: Love_Missile_F1-11 - Sigue_Sigue_Sputnik - 00:04:07

Remise de votre solution

Pour cette mission, vous devez soumettre votre programme mission8.py, un fichier test.py avec vos fonctions de tests pour les différentes classes que vous avez implémenté et votre fichier README.txt au serveur de soumissions de programmes du cours.

Votre fichier mission8.py doit au moins contenir les classes Duree, Chanson et Album, ainsi qu'une série d'instructions pour lire le fichier et créer et imprimer les albums.

Votre fichier test.py doit au moins contenir des tests pour les différentes méthodes des classes Duree, Chanson et Album, ainsi qu'une série d'instructions pour lancer tous les tests.

L'exécution de votre programme mission8.py doit imprimer l'output ci-dessus sur base du fichier original "music-db.txt". Il ne vous est pas permis de faire des modifications à ce fichier.


        
        
Questions complémentaires

Questions complémentaires

SMS Store

Create a new class SMSStore. The class will instantiate SMS_store objects, which can store SMS messages, similar to an inbox or outbox on a cellphone:

my_inbox = SMS_store()

This store can hold multiple SMS messages (its internal state will just be a list of messages). Each message will be represented as a tuple: (has_been_viewed, from_number, time_arrived, text_of_SMS)

The inbox object should provide these methods:

my_inbox.add_new_arrival(from_number, time_arrived, text_of_SMS)
    # Makes a new SMS tuple, and inserts it after the other messages
    # in the store. When creating this message,
    # its has_been_viewed status is set False.

my_inbox.message_count()
    # Returns the number of sms messages in my_inbox

my_inbox.get_unread_indexes()
    # Returns list of indexes of all not-yet-viewed SMS messages

my_inbox.get_message(i)
    # Return (from_number, time_arrived, text_of_sms) for message[i]
    # Also change its state to "has been viewed".
    # If there is no message at position i, return None

my_inbox.delete(i)
    # Delete the message at index i

my_inbox.clear()
    # Delete all messages from inbox

Write the class ``SMS_store``, create a message store object,
write tests for each of the above methods, and implement the methods.


        
        

Création d'objets depuis un fichier

Dans cet exercice, il vous est demandé de créer une fonction créant des instances de la classe Student basées sur les informations contenus dans un fichier, et les renvoyant dans une liste. Ceci afin de récupérer les notes de l'interros des différents participants.

Voici le constructeur de la classe Student:

class Student:
    def __init__(self, firstname, surname, mark):
        self.firstname = firstname
        self.surname = surname
        self.mark = mark

Le fichier suit le format suivant:

Kim Mens 12
Charles Pecheur 13
Siegfried Nijssen 18

Implémentez la fonction marks_from_file(filename) qui lit, ligne par ligne, le fichier nommé filename, et qui retourne une liste d'instances de Student, correspondant aux différents lignes du fichier.


        
        

<string>

Table des matières

Mission 9 - Héritage

Mission 9 : Héritage

Mission 9 : Héritage

Introduction

Le traitement de données commerciales et financières est un des champs majeurs d'application de l'informatique. De nos jours, la moindre PME utilise l'informatique pour gérer sa comptabilité, ses commandes et sa facturation, et l'infrastructure informatique est devenue un élément critique de toute grande entreprise, dont la supervision représente un poste clé au sein de l'exécutif, celui de Chief Information Officer ou CIO.

Invoice Manager, un logiciel de facturation pour PME. (© Hillstone Software)

PCLine s.p.r.l., une société de vente de matériel et de service informatique locale de Louvain-la-Neuve, fait appel à vos services pour développer ses applications informatisées de facturation. Les gérants de PCLine disposent déjà d'un programme pour imprimer leurs factures et calculer la TVA et les totaux. Ils désirent développer également la livraison des articles et de pièces, en intégrant l'impression des bordereaux de livraison dans leur logiciel de facturation existant, sans devoir écrire complètement leur logiciel existant. Heureusement, leur logiciel étant écrit en Python dans un style orienté objets, leur consultant de l'UCL leur a confirmé qu'il était possible de l'étendre sans le modifier au moyen des mécanismes d'héritage de Python. Ils ont donc confié aux étudiants de 1er Bac la tâche d'ébaucher la représentation des pièces en catalogue, leur intégration dans la facturation, ainsi que l'impression des bordereaux de livraison en plus de l'impression des factures.

Objectifs

A l'issue de cette mission, chacun d'entre vous :

  • pourra expliquer en ses propre mots et illustrer par l'exemple les principes de l'héritage en Python:

    • l'héritage d'une classe, la redéfinition de méthodes;
    • les notions d'héritage, de polymorphisme, et d'hiérarchie de classes;
    • l'utilisation de self et super();
  • sera capable d'utiliser l'héritage pour étendre un programme Python;

  • pourra rendre privé les variables d'instance d'une classe et écrire des méthodes pour y accéder;

  • aura appris les notions de variables et méthodes de classe et leur utilité;

  • pourra utiliser les méthodes magiques comme :

    • __init__ pour initialiser les objets d'une classe;
    • __str__ pour retourner une représentation textuelle d'un instance d'une classe;
    • __eq__ pour définir l'égalité entres objets d'une classe;

Préparation, étude et apprentissage

La matière relative à cette mission est décrite dans les sections suivantes de la partie Objects du syllabus en ligne:

ainsi que les annexes:

Questionnaire de démarrage

Questions à choix multiple

Les questions à choix multiples de cette mission sont accessibles en ligne depuis https://inginious.info.ucl.ac.be/course/LSINF1101-PYTHON/Session10_QCM


        
        

Questions ouvertes

Question 1 : Héritage

L'héritage est un principe de base de la programmation orientée objet. Considérons les classes A, B, C et D ci-dessous :

class A :

  def m1(self) :
      print("A 1")

  def m2(self) :
      print("A 2")

  def m3(self) :
      self.m1()   # appel à la méthode m1 sur la même instance

  def nom(self) :
      return "A"

class B(A) :

  def m2(self):
      print("B 2")

class C(A):

    def m1(self) :
        print("C 1")

    def nom(self):
        return "C"

class D(C) :

    def m2(self) :
        print("D 2")

Considérant ces quatre classes, on vous demande de :

  • Expliquez ce que représente le mot self dans la définition de ces différentes méthodes.
 
 
 
 

  • Dessiner un diagramme reprenant ces quatre classes.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

  • Expliquer ce qui sera affiché lors de l'exécution des instructions Python ci-dessous :
a = A()
print(a.nom())
a.m1()
a.m2()
a.m3()
b = B()
print(b.nom())
b.m1()
b.m2()
b.m3()
c = C()
print(c.nom())
c.m1()
c.m2()
c.m3()
d = D()
print(d.nom())
d.m1()
d.m2()
d.m3()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Question 2 : super()

  • Expliquez le rôle de la fonction spécial super() dans le langage Python. Donnez un exemple.
 
 
 
 
 

  • Considérons les classes E et F ci-dessous :
class E :

    def m(self) :
        print("E 1")

    def n(self) :
        print("E 2")

    def p(self) :
        self.n()   # appel à la méthode m1 sur la même instance

class F(E) :

   def q(self) :
       print("F 1")

   def n(self) :
       super().n() # appeler la méthode définie sur la classe mère
       print("F 2")

   def r(self) :
        self.m() # appel à la méthode m1 sur la même instance

Expliquer ce qui sera affiché lors de l'exécution des instructions Python suivantes :

f = F()
f.q()
f.m()
f.r()
f.n()
f.p()
 
 
 
 
 
 
 

Question 3 : Rectangle

Considérons la classe Figure reprise ci-dessous :

class Figure:

    def __init__(self,x,y,visible=False) :
        """
        @pre x, y sont des entiers représentant des positions sur l'écran
        @post Une figure a été créée avec centre de gravité aux coordonnées x,y.
              Cette figure n'est initialement pas visible.
        """
        self.x = x
        self.y = y
        self.__visible = visible

    def estVisible(self) :
        """
        @pre -
        @post a retourné la visibilité de cette figure
        """
        return self.__visible

    def surface(self) :
        """
        @pre -
        @post la surface (un float) de la figure a été calculé et retournée
        """
        pass            # code non fourni

Cette classe Figure est la classe mère d'un ensemble de classes permettant de représenter des figures géométriques. Chaque figure géométrique est placée à une position (x,y) (centre de gravité) sur l'écran et la classe contient des variables d'instance et des méthodes permettant de manipuler cette figure géométrique (notamment des méthodes permettant d'afficher la figure à l'écran, mais ces méthodes ne sont pas reprises dans les extraits présentés dans cet exercice). Parmi ces figures géométriques, on trouve notamment la classe Rectangle qui hérite de la classe Figure et dont un fragment est repris ci-dessous :

class Rectangle(Figure):

    def __init__(self,lo,la,x,y) :
        """
        @pre lo (longeur) et la (largeur) sont des entiers positifs
             x, y sont des entiers représentant des positions sur l'écran
        @post un rectangle dont le centre de gravite est en x,y
              et ayant comme longueur lo et comme largeur la a été créé
        """
        super().__init__(x,y)
        self.longeur = lo
        self.largeur = la

    def __str__(self) :
        return str((self.longeur,self.largeur,self.x,self.y,self.estVisible()))

>>> r = Rectangle(10,20,0,0)
>>> print(r)
(10, 20, 0, 0, False)

Maintenant expliquez :

  • Quelles sont les variables d'instance qu'une instance de la classe Rectangle peut utiliser ?
  • Que se passe-t il lorsqu'une instance r de la classe Rectangle est créé?
  • Que fait l'appel à super() dans la méthode __init__ de la classe Rectangle ?
  • Que se passe-t il si on met cet appel à super() comme dernière instruction dans la méthode __init__ de la classe Rectangle ?
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Dans la classe Rectangle, faut-il redéfinir les méthodes suivantes (si oui, écrivez le code de la nouvelle méthode - si non, expliquez pourquoi ):

  • surface()
 
 
 
 
 
 
 
 

  • estVisible()
 
 
 
 

Question 4 : Variables privés

Pour éviter de rendre accessible les variables x, y et visible à l'extérieur d'une Figure, quelqu'un modifie le code de la classe Figure. Concrètement, pour rendre ces variables privés, on ajoute un __ à leur nom, comme suit :

class Figure:

    def __init__(self,x,y,visible=False) :
        """
        @pre x, y sont des entiers représentant des positions sur l'écran
        @post Une figure a été créée avec centre de gravité aux coordonnées x,y.
              Cette figure n'est initialement pas visible
        """
        self.__x = x
        self.__y = y
        self.__visible = visible


    def estVisible(self) :
        """
        @pre -
        @post a retourné la visibilité de cette figure
        """
        return self.__visible

    def surface(self):
        """
        @pre -
        @post la surface (un float) de la figure a été calculé et retournée
        """
        pass            # code non fourni

Malheureusement, le code de la classe Rectangle, qui hérite de la classe Figure ne marche plus maintenant. Quel est le problème?

 
 
 
 

Comment peut-on corriger ce problème? Corrigez le code afin que les instructions suivantes produisent le résultat voulu.

>>> r = Rectangle(10,20,0,0)
>>> print(r)
(10,20,0,0,False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Question 5 : Carre

Comment feriez-vous maintenant pour définir une classe Carre qui étend la classe Rectangle et permet de représenter un carré ?

  • Ecrivez la méthode d'initialisation de la classe Carre (un carré se construit en indiquant les coordonnées de son centre de gravité et la longueur de son côté)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

  • Quelles sont les méthodes que vous devez redéfinir dans la classe "Carre" ?
 
 
 
 

  • Soient deux classes Circle et Ellipse. Quelles relations peut-on envisager entre ces deux classes ?
 
 
 
 

  • Ecrivez une méthode perimetre() de la classe Rectangle qui retourne le périmètre du rectangle.
 
 
 
 

  • Que faudrait-il faire pour avoir une méthode perimetre de la classe Carre?
 
 
 
 

Question 6 : Similitude

Définissez une méthode __eq__ pour la classe Figure, telle que deux figures sont égales si leur surface est égale.

 
 
 
 

Que se passe-t il si on veut comparer deux rectangles ayant la même surface? Par exemple:

>>> r1 = Rectangle(10,40,0,0)
>>> r2 = Rectangle(2,200,0,0)
>>> r1 == r2
 
 
 
 

Que se passe-t il si on veut comparer un rectangle avec un carré ayant la même surface? Par exemple:

>>> r = Rectangle(10,40,0,0)
>>> c = Carre(20,0,0)
>>> print(r == c)
 
 
 
 

Question 7 : Ticket

Complétez la classe Ticket ci-dessous:

"""
Un ticket de parking
"""
class Ticket :

    __prochainnumero = 1  # variable de classe pour générer le numéro du ticket

    def __init__(self) :
        """
        @pre  -
        @post Crée un ticket avec un nouveau numéro.
              Les numéros sont attribués séquentiellement à partir de 1.
        """
            # A COMPLETER

    def numero(self):
        """
        @pre  -
        @post retourne le numero de billet
        """
        return self.__numero


        
        

Mission 9 : Héritage

Mission 9 : Héritage

Description

Pour cette mission, vous disposez au départ d'un programme simple permettant d'imprimer des factures. Ce programme comporte principalement deux classes: l'une représente un Article, c'est-à-dire une ligne dans la facture; l'autre représente une Facture sous forme d'une liste d'articles et offre des méthodes permettant de l'imprimer.

Une classe de test initiale Test est également fournie, elle produit l'exemple de facture suivant:

Facture PC Store - 22 novembre
===================================================================================
| Description                              |  prix HTVA |        TVA |  prix TVAC |
===================================================================================
| laptop 15" 8GB RAM                       |     743.79 |     156.20 |     899.99 |
| installation windows                     |      66.11 |      13.88 |      79.99 |
| installation wifi                        |      45.22 |       9.50 |      54.72 |
| carte graphique                          |     119.49 |      25.09 |     144.58 |
===================================================================================
| T O T A L                                |     974.61 |     204.67 |    1179.28 |
===================================================================================

Une version plus étendue de cette classe de test est également fournie; elle vous permettra de tester les extensions que vous devez développer.

Votre objectif principal est de développer des classes qui étendent Article , en offrant des fonctionnalités supplémentaires tout en restant utilisables par la classe Facture . Dans un deuxième temps, vous ajouterez aussi une méthode à la classe Facture qui exploite ces nouvelles fonctionnalités. Votre programme complet devrait être capable de calculer et générer des factures et bons de livraison tels que celui-ci:

Facture No 1 : Facture PC Store - 22 novembre
===================================================================================
| Description                              |  prix HTVA |        TVA |  prix TVAC |
===================================================================================
| laptop 15" 8GB RAM                       |     743.79 |     156.20 |     899.99 |
| installation windows                     |      66.11 |      13.88 |      79.99 |
| installation wifi                        |      45.22 |       9.50 |      54.72 |
| carte graphique                          |     119.49 |      25.09 |     144.58 |
| 1 * disque dur 350 GB @ 49.99            |      49.99 |      10.50 |      60.49 |
| 3 * souris bluetooth @ 15.99             |      47.97 |      10.07 |      58.04 |
| Réparation (0.75 heures)                 |      46.25 |       9.71 |      55.96 |
| 5 * adaptateur DVI - VGA @ 12.00         |      60.00 |      12.60 |      72.60 |
| 2 * Java in a Nutshell @ 24.00           |      48.00 |       2.88 |      50.88 |
| 5 * souris bluetooth @ 15.99             |      79.95 |      16.79 |      96.74 |
===================================================================================
| T O T A L                                |    1306.77 |     267.22 |    1573.99 |
===================================================================================

Livraison - Facture No 1 : PC store 22 octobre
===================================================================================
| Description                              |  poids/pce |     nombre |      poids |
===================================================================================
| disque dur 350 GB (!)                    |    0.355kg |          1 |    0.355kg |
| souris bluetooth                         |    0.176kg |          3 |    0.528kg |
| adaptateur DVI - VGA                     |    0.000kg |          5 |    0.000kg |
| Java in a Nutshell                       |    0.321kg |          2 |    0.642kg |
| souris bluetooth                         |    0.176kg |          5 |    0.880kg |
===================================================================================
| 5 articles                               |            |         16 |    2.405kg |
===================================================================================
 (!) *** livraison fragile ***

Voici les étapes à suivre:

Etape 0

Chargez les fichiers et étudiez leurs contenus. Vous disposez des fichiers suivants:

  1. article.py : cette classe représente un article de la facture. Elle contient un descriptif et un prix et offre des méthodes permettant d'obtenir ces données et de calculer le prix avec TVA. Analysez attentivement le code de cette classe. Remarquez comment les différentes méthodes font appel à prix() . Ceci est important quand on étend cette classe: en redéfinissant prix() on modifiera aussi indirectement les autres méthodes qui y font appel.
  2. facture.py: cette classe représente une facture. Elle contient une liste d'articles. Elle offre principalement une méthode __str__ qui retourne un string détaillé représentant la facture, qui peut être imprimé avec la méthode print(), ainsi que plusieurs méthodes auxiliaires utilisées par cette méthode __str__.
  3. test.py: qui fonctionne avec les classes fournies initialement. Elle produit l'impression reprise dans le premier exemple ci-dessus.

Vous devrez vous-mêmes créer au moins deux classes ArticleReparation et ArticlePiece qui étendent Article, une classe Piece , ainsi que deux méthodes supplémentaires dans Facture , selon les instructions qui suivent.

Etape 1

  • Dans le fichier mission9.py , créez une nouvelle classe ArticleReparation qui étend Article . Cette classe représente une prestation de réparation de durée donnée (un float, en heures).
  • Définissez une méthode d'initialisation avec la durée en paramètre.
  • Re-définissez la méthode description() pour fournir un descriptif adéquat comme Reparation (0.75 heures) .
  • Re-définissez la méthode prix() pour calculer un coût fixe de 20 euro plus un coût variable de 35 euro/h.
  • Modifiez la classe TestFactureInitial ou créez une nouvelle classe teste TestFactureEtape1 pour tester votre classe.

Etape 2

  • Créez une nouvelle classe "Piece" qui représente une pièce que l'on peut facturer. Elle comporte les données suivantes:

    • une description (string), p.ex. 'souris bluetooth';
    • un prix unitaire (float), p.ex. 15.99 Euro;
    • un poids unitaire en kg (float), p.ex. 0,154 kg;
    • un indicateur booléen indiquant si la pièce est fragile, p.ex. un disque dur est fragile mais pas une souris;
    • un indicateur booléen indiquant si la pièce est à taux de TVA réduit, p.ex. les livres bénéficient de TVA réduite.
  • Ajoutez une méthode d'initialisation permettant d'initialiser toutes ces données. Cette méthode d'initialisation doit aussi être utilisable avec seulement deux paramètres (description, prix) pour les pièces de poids négligeable, non fragiles et à taux de TVA normal.

  • Ajoutez des méthodes accèsseurs (méthodes description() , prix() , poids() , fragile() , tva_reduit()) pour toutes ces données.

  • Ajoutez une méthode magique __eq__ afin que deux pièces sont considérées égales ( == ) si elles ont la même description et le même prix (les autres données sont ignorées pour la comparaison).

Etape 3

  • Créez une nouvelle classe ArticlePiece qui étend Article et représente l'achat d'un nombre donné d'une pièce donnée.
  • Ajoutez une méthode d'initialisation prenant le nombre et la pièce en paramètres, ainsi que des méthodes accèsseurs pour ces deux données.
  • Re-définissez la méthode description() pour fournir un texte reprenant la description de la pièce, le nombre et le prix unitaire, par exemple: 3 * souris bluetooth @ 15.99 .
  • Re-définissez la méthode prix() pour faire le produit du prix unitaire de la pièce par le nombre de pièces.
  • Re-définissez la méthode tva() pour appliquer un taux de 6% aux pièces à taux de TVA réduit.
  • Modifiez la classe TestFactureInitial ou créez une nouvelle classe teste TestFactureEtape3 pour tester votre classe.

Etape 4

  • Modifiez la classe Facture pour que chaque nouvelle facture reçoive un numéro séquentiel unique, qui apparait dans l'en-tête de la facture.
  • Dans la classe Facture, ajoutez une méthode def nombre(self,pce) qui retourne le nombre d'exemplaires de pce dans la facture, en totalisant sur tous les articles qui concernent cette pièce.
    • Vous pouvez utiliser le fait que la méthode "==" a été redéfinie dans la classe "Piece" (via la méthode magique __eq__ que vous avez ajoutée).

Etape 5

Dans la classe Facture, ajoutez une méthode printLivraison() qui imprime un bordereau de livraison comme

Livraison - Facture No 1 : PC store 22 octobre
===================================================================================
| Description                              |  poids/pce |     nombre |      poids |
===================================================================================
| disque dur 350 GB (!)                    |    0.355kg |          1 |    0.355kg |
| souris bluetooth                         |    0.176kg |          3 |    0.528kg |
| adaptateur DVI - VGA                     |    0.000kg |          5 |    0.000kg |
| Java in a Nutshell                       |    0.321kg |          2 |    0.642kg |
| souris bluetooth                         |    0.176kg |          5 |    0.880kg |
===================================================================================
| 5 articles                               |            |         16 |    2.405kg |
===================================================================================
 (!) *** livraison fragile ***

Ce bordereau: * imprime une en-tête avec la description de la facture; * imprime toutes les pièces dans la facture avec, pour chacune, sa description, son poids unitaire, le nombre facturé et le poids correspondant; * ajoute une marque dans la description des pièces fragiles; * totalise et imprime à la fin le nombre d'articles, le nombre de pièces et le poids total; * imprime un message supplémentaire si (et seulement si) la livraison contient une ou plusieurs pièces fragiles.

Remarquez que les détails imprimés dans ce bordereau de livraison ne concernent que les articles de type ArticlePiece; les autres articles sont ignorés. Pour faciliter le formatage du texte, vous pouvez utiliser la méthode format, déjà utilisée à plusieurs endroits dans la classe Facture. Si vous ne la connaissez pas, n'hésitez pas à chercher en-ligne comment cette méthode format fonctionne exactement. Pour implémenter la méthode printLivraison() , réutiliser un maximum de méthodes déjà existantes dans la classe Facture .

Finalement, modifiez la classe TestFactureInitial ou créez une nouvelle classe teste TestFactureEtape5 pour tester votre nouvelle méthode.

Remise de votre solution

Pour cette mission, vous devez soumettre toutes les classes de votre programme dans un fichier mission9.py, vos classes tests dans un fichier test.py, ainsi que votre fichier README.txt qui décrit comment on peut tester votre code.

Votre fichier mission9.py doit contenir les classes Facture, Article, ArticleReparation, Piece et ArticlePiece.

Votre fichier test.py doit contenir soit votre classe TestFactureInitial modifiée, soit les classes TestFactureInitial, TestFactureEtape1, TestFactureEtape3 et TestFactureEtape5, ainsi qu'une série d'instructions à la fin pour lancer tous les tests automatiquement lors de l'exécution du fichier.

L'exécution de votre programme mission9.py doit imprimer une facture et un bon de livraison comme illustrer plus haut dans ce document.


        
        

Challenge

Il est possible d'ajouter de multiples variantes d'article à ce programme. Par exemple:

  • étendre ArticlePiece en ArticlePieceGros qui applique une dégression sur les prix en fonction du nombre commandé;
  • ajouter une extension d'Article qui fait référence à une configuration de PC et calcule le prix correspondant (taille mémoire, CPU, écran, capacité disque, etc);
  • étendre ArticleReparation en ArticleReparationUrgente avec des tarifs plus élevés;
  • ajouter une extension d'Article pour des frais de déplacement;
  • ...

On peut également ajouter des calculs supplémentaires sur les factures, par exemple un bilan des frais de main d'oeuvre. Un peu plus difficile, modifier la méthode printLivraison() de la classe Facture pour n'imprimer qu'une seule ligne par pièce qui cumule les articles correspondants, comme dans la méthode nombre() .

Questions complémentaires

Questions complémentaires

Variable de classe

Dans cet exercice, il vous est demandé de programmer une classe Student . Chaque instance de cette classe représente un étudiant, ayant les caractéristiques suivantes :

  • Un prénom
  • Un nom de famille
  • Une date de naissance
  • Un courriel
  • Un NOMA (un numéro d'identification différent pour chaque étudiant et donné à la création de l'objet)

La représentation en format string de cet objet doit être, par exemple : _L'étudiant 27: Hervé Ducobu né le 1 avril, peut être contacté par **herve.ducobu@student.uclouvain.be**_ Pour cela vous êtes invités à implémenter la méthode __str__ .

System Message: WARNING/2 (<string>, line 20); backlink

Inline strong start-string without end-string.

Pour générer un numéro d'identification unique différent pour chaque étudiant, vous êtes conseillé à utiliser une variable de classe pour retenir le dernier numéro généré. Ce numéro est augmenté lors de chaque nouvelle création d'instance de cette classe. Il est déconseillé d'utiliser une variable globale au lieu d'une variable de classe.


        
        

Méthodes de classe

En tant que nouveau manager d'un centre d'expédition d'Amazon, vous devez créer une classe pour représenter les commandes.

Une commande a les attributs suivants :

l'id de l'acheteur ( id_buyer ) l'id de l'objet ( id_item ) la quantité ( quantity ) le prix de l'objet ( price )

Et doit avoir les méthodes suivantes :

  • get_price() : retourne le prix total de la commande
  • __str__() : retourne la commande sous le format : id_buyer, id_item : price * quantity = (price*quantity) Exemple : "12, 32 : 5 * 20 = 100"

Il doit aussi y avoir des méthodes de classe :

get_number_total_command() : retourne le nombre total de commandes effectuées get_total_price() : retourne le prix total de toutes les commandes

Nous allons créer l'objet avec :

command = Command(id_buyer, id_item, quantity_item, price_item)


        
        

Héritage

Commencez par implémenter une classe Animal, qui devrait avoir :

  • un attribut name qui est le nom que le joueur donnera à l'animal
  • un attribut diurnal qui doit être True si l'animal est actif pendant la journée et False s'il est actif pendant la nuit
  • un attribut nb_legs qui représente le nombre de pattes qu'a un animal
  • un attribut asleep qui sera mis à True (resp. False) avec l'appel à la fonction sleep() (resp. wake_up()). Si l'animal essaye de se réveiller alors qu'il n'est pas endormi ou s'il essaye de dormir alors qu'il dort déjà, votre programme devrait lever une RuntimeError.

Seul le name de l'animal doit être passé en argument au constructeur. Nous avons décidé de mettre asleep à False par défaut. Evidemment, comme cette classe représente n'importe quel animal, nous ne connaissons pas la valeur que les autres attributs devraient prendre. La chose la plus logique à faire serait donc de les mettre à None.

Vous implémenterez ensuite trois sous-classes différentes d'Animal réprésentant trois espèces différentes : Lion, Owl et Giraffe. Les valeurs des différents attributs de la classe parent devraient maintenant être définies dans les méthodes d'initialisation.

Lion aura une méthode additionnelle roar(), qui affichera ROARRR!!!.

Owl aura une méthode additionnelle fly(), qui ne fera rien (utilisez l'expression pass pour ne rien faire).

Giraffe aura un attribut additionnel neck_length (en mètres). Cette information sera donnée au constructeur de la classe. Notez que votre code ne devrait pas accepter de valeurs invalides pour cet attribut (les tailles négatives ou exprimées en chaines de caractères par exemple). Levez une ValueError dans le cas où vous avez une valeur invalide.

Vous implémenterez ensuite une dernière classe : Zoo , qui aura une liste de tous les animaux appelée animals comme attribut et une méthode add_animal(animal) pour remplir cette liste. Levez une ValueError si cette méthode reçoit quelque chose d'autre qu'un animal.

On vous demande enfin de définir une fonction create_my_zoo() qui retournera votre zoo contenant un lion, une chouette et deux girafes. Ce sont vos animaux, appelez les comme vous voulez ;)


        
        
<string>

Table des matières

Mission 10 - Polymorphisme

Mission 10

Mission 10

Introduction

Objectifs

Dans cette mission, vous allez approfondir votre compréhension de concepts importants de la programmation orientée objets déjà abordés dans les deux missions précédentes, ainsi que quelques nouveaux concepts :

  • les classes, objets, variables et méthodes instance ;
  • les variables et méthodes de classe ;
  • les variables et méthodes privés, les méthodes accesseurs et mutateurs ;
  • la portée des variables d'instance ;
  • l'héritage, la redéfinition et l'écrasement de méthodes, le polymorphisme ;
  • la liaison dynamique et les subtilités de la sémantique de self et super().

Vous aurez également l'occasion d'écrire des tests unitaires pour vos programmes orientés objets.

Préparation, étude et apprentissage

Si vous n'avez pas encore eu le temps de relire en détail les différents chapitres de la partie Objects du syllabus en ligne, maintenant est venu le moment de rattraper votre retard. Le syllabus explique bien différents concepts, sur un autre exemple que les exemples utilisés dans le cours magistral :

On vous conseille également de tester et de jouer avec les différents exemples de code qui se trouvent dans les annexes suivantes, venant soit du syllabus en ligne soit correspondant aux exemples du cours magistral :

Dans cette mission, vous devrez également utiliser le framework unittest pour produire des classes de test. En préparation de cette mission, consultez donc la documentation de ce cadre de test, et essayez de faire quelques classes test simple en utilisant cet approche. Un bon tutoriel (en français) sur les tests unitaires avec unittest se trouve également ici Créez des tests unitaires avec unittest

Questionnaire de démarrage

Questions à choix multiple

Les questions à choix multiples de cette mission sont accessibles en ligne depuis https://inginious.info.ucl.ac.be/course/LSINF1101-PYTHON/Session10_2019_QCM


        
        

Questions ouvertes

Question 1 : Vocabulaire

On vous donne le code suivant qui teste certaines fonctionnalités du module random.

import unittest
import random

class RandomTest(unittest.TestCase):
    """Clases de test utilisé pour tester les fonctions du module 'random'"""

    def setUp(self):
        """Initialisation des tests."""
        self.l = list(range(10))

    def test_choice(self):
        """Test de fonctionnement de la fonction random.choice"""
        e = random.choice(self.l)
        # Vérifie que 'e' est dans 'l'
        self.assertIn(e,self.l)

    def test_shuffle(self):
        """Test le fonctionnement de la fonction random.shuffle"""
        random.shuffle(self.l)
        self.l.sort()
        self.assertEqual(self.l, list(range(10)))

    def test_sample(self):
        """Test le fonctionnement de la fonction random.sample"""
        extrait = random.sample(self.l, 5)
        for element in extrait:
            self.assertIn(element,self.l)
        self.assertRaises(ValueError, random.sample, self.l, 20)

# To automatically launch this test when executing this file
if __name__ == '__main__':
    unittest.main()

Considérant cette classe, on vous demande de :

  • Expliquez cet extrait de code

    if __name__ == '__main__':
        unittest.main()
    
 
 
 
 

  • Expliquez le rôle de import unittest et pourquoi c'est indispensable.
 
 
 
 

  • Expliquez cette ligne class RandomTest(unittest.TestCase): et pourquoi c'est indispensable.
 
 
 
 

  1. def setUp(self)
 
 
 
 

  1. self.assertEqual()
 
 
 
 

  1. self.assertIn()
 
 
 
 

  1. self.assertRaises()
 
 
 
 

Question 2 : Tests unitaires

Implémentez une classe de tests unitaires MyClassTest qui hérite de unittest.TestCase qui teste la fonction carre_parfait du module totest. (En mathématiques, un carré parfait est le carré d'un entier.)

Soit la fonction carre_parfait du module totest ayant pour spécifications :

def carre_parfait (x) :
    '''
    retourne true si l'entier en argument est un carré parfait, false sinon
    '''
# CODE NON FOURNI

Complétez la classe de test ci-dessous :

import totest
import unittest

class MyClassTest(unittest.TestCase) :

    # todo

if __name__ == '__main__':
    unittest.main()
  • Ecrivez au moins trois tests unitaires pour vérifier l'implémentation de cette fonction
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

  • Que donnent-vos tests unitaires pour les implémentations suivantes :

    1. def carre_parfait (x) :
         return True
      
    2. def carre_parfait (x) :
          return False
      
    3. def carre_parfait (x) :
          cur = 0
          while not cur*cur == x :
              cur += 1
          return True
      
    4. import math
      def carre_parfait (x) :
          root = math.sqrt(x)
          return root.is_integer()
      
    5. import math
      def carre_parfait (x) :
          if x < 0 : return False
          root = math.sqrt(x)
          return root.is_integer()
      

Question 3 : Amazon

As the new manager of an Amazon dispatch center, you have to create a class to represent the commands.

A command have the following attributes :

  • the id of the buyer
  • the id of the item
  • the quantity of the item
  • the price of the item

And must have the following methods :

  • get_price() : return the total price of the command
  • __str__() : return the command on the format : id_buyer, id_item : price * quantity = (price*quantity)

Example : "12, 32 : 5 * 20 = 100"

There must also be class methods :

  • get_number_total_commad() : return the total number of commands made
  • get_total_price() : return the total price of all the commands


        
        

Mission 10

Mission 10

L'objectif de cette mission est de créer des robots maintenant un état représentant leur position à l'écran et des opérations permettant de les faire bouger sur l'écran. On peut se baser sur plusieurs bibliothèques graphiques pour implémenter de tels robots. On vous fournira une première implémentation basé sur la bibliothèque graphics.py de Python. A vous de proposer une implémentation similaire en utilisant la bibliothèque turtle. Ces deux types de robots sont polymorphe: ils comprennent exactement les mêmes messages, mais l'un est implémenté avec des positions absolues sur un plan XY, tandis que l'autre utilise des positions relatives. Ensuite on vous demandera d'étendre la fonctionnalité de ces robots, ce qui vous permettra de découvrir la puissance du concept de l'héritage pour la réutilisation du code.

Il sera aussi important de bien tester les classes qui vous sont fournies ou que vous implémenterez, avec des tests unitaires. C'est-à-dire que vous créerez, par classe à tester, une classe test correspondante, avec des méthodes tests pour chacune des méthodes de la classe à tester. Ceci vous permet de tester, à une granularité fine, les différents unités (les classes et les méthodes) de votre code orienté objet.

Les différents concepts traités dans cette mission seront, entre autres : le polymorphisme, la composition, la délégation, l'héritage, la duplication et la réutilisation de code, la substitutabilité, l'utilisation de self et de super, et les tests unitaires. Vous travaillerez par groupes de deux.

Etapes

Etape 1 : Tester la classe XYRobot

L'objectif de cette première étape sera de créer une classe test TestXYRobot avec des tests unitaires pour une classe XYRobot qui vous est fournie.

  • Avant de commencer, vous devez installer le package graphics.py permettant de dessiner des figures simples dans une fenêtre graphique. Cette bibliothèque sera utilisée par la classe XYRobot. Vous pouvez installer ce package facilement depuis Thonny en suivant les étapes suivantes: (1) Sélectionner le menu Tools > Manage Packages... en Thonny; (2) taper graphics.py dans le champs de recherche pour retrouver le bon package et appuyez sur Install .

  • La classe XYRobot utilise aussi des fonctions mathématiques comme math.cos et math.sin ainsi que des constantes mathématiques comme math.pi, fournies par la bibliothèque math.

  • La classe XYRobot (dont l'implémentation se trouve dans le fichier XYRobot.py qui vous est fourni sur le site inginious où vous pouvez remettre votre solution) représente un robot ayant un nom, une position (x, y), et une direction (angle). Quand il bouge, ce robot dessine ses mouvements dans une fenêtre représentant un plan XY.

  • En plus des méthodes __init__ et __str__ et des méthodes accesseurs et mutateurs, cette classe implémente des méthodes comme:

    moveforward(distance) :

    fait avancer le robot de distance pixels et trace une ligne lors de ce mouvement

    movebackward(distance) :

    fait reculer le robot de distance pixels et trace une ligne lors de ce mouvement

    turnleft() :

    fait tourner le robot de 90 degrés vers la gauche (dans le sens contraire des aiguilles d'une montre)

    turnright() :

    fait tourner le robot de 90 degrés vers la droite (dans le sens des aiguilles d'une montre)

    Appeler ces méthodes fait bouger le robot et dessine des figures sur un plan XY. Il suffit d'exécuter le code fourni dans le fichier pour avoir un exemple concret. N'oubliez pas d'installer d'abord la bibliothèque graphics.py avant d'exécuter ce fichier.

L'implémentation de cette classe XYRobot vous est fournie mais il manque une classe avec des tests unitaires afin de vérifier que le robot bouge et met à jour ses attributs correctement. Implémentez cette classe de test TestXYRobot afin de vérifier le comportement correct des différentes méthodes de la classe XYRobot.

Par exemple, quand on crée un nouveau robot r2d2 = XYRobot("R2-D2") il se trouvera à la position (x=100,y=100) avec comme angle 0 (direction Est). Après avoir exécuté r2d2.moveforward(50) le robot devrait se trouver à la position (x=150, y=100), approximativement, à cause des arrondis dans les calculs. Ensuite, après avoir exécuté r2d2.turnleft() l'angle du robot sera 90° (approximativement), etc.

Soyez le plus complet possible dans vos tests. Mettez votre classe test TestXYRobot dans un fichier séparé TestXYRobot.py qui s'occupera de charger le fichier XYRobot.py et d'exécuter les différents tests sur la classe XYRobot.

Etape 2 : implémenter une classe TurtleBot

Dans cette deuxième étape c'est à vous d'implémenter une classe TurtleBot qui est polymorphe avec la clase XYRobot de l'étape précédente. C'est-à-dire que, en plus des méthodes __init__ et __str__ et des méthodes accesseurs (dont getangle() et position()) et mutateurs, elle doit implémenter les mêmes méthodes :

moveforward(distance) :
fait avancer le robot de distance pixels et trace une ligne lors de ce mouvement
movebackward(distance) :
fait reculer le robot de distance pixels et trace une ligne lors de ce mouvement
turnleft() :
fait tourner le robot de 90 degrés vers la gauche (dans le sens contraire des aiguilles d'une montre)
turnright() :
fait tourner le robot de 90 degrés vers la droite (dans le sens des aiguilles d'une montre)

La différence majeure est que la classe TurtleBot fait ces dessins via un objet de type Turtle. Cette classe sera donc assez facile à implémenter en utilisant un objet de type Turtle comme attribut. Le travail d'avancer, tourner et dessiner pourra alors être délégué à ce turtle.

Pour tester son comportement correct, on vous fournit une fichier TestTurtleBot.py (ce fichier se trouve sur le site inginious où vous pouvez remettre votre solution.) Ce fichier test contient des tests unitaires pour votre classe TurtleBot qui doit se trouver dans un fichier TurtleBot.py.

Vous pouvez aussi vérifier que le comportement de votre robot est correct en vérifiant que ce qu'il dessine sur l'écran correspond à ce que vous attendez.

Etape 3 : étendre les classes XYRobot et TurtleBot

Maintenant, on veut étendre la fonctionnalité des robots. A vos deux classes XYRobot et TurtleBot , ajoutez des méthodes pour:

  • garder une trace des actions exécutées auparavant par ce robot
  • rejouer ces action à l'envers (c'est-à-dire de défaire ces actions).

Plus spécifiquement, il faut ajouter les deux méthodes suivantes:

getHistory(self) :

retourne l'historique des actions précédentes, comme une liste du style:

[('forward', 50), ('left', 90), ('forward', 50), ...]

unplay(self) :
rejouer les actions précédentes dans le sens inverse et vider l'historique après avoir rejoué ces actions

Etape 4

Ajouter aux deux classes de test TestXYRobot et TestTurtleBot les tests unitaires nécessaires pour tester si les deux classes XYRobot et TurtleBot, avec la fonctionnalité historique et la fonctionnalité pour défaire les actions, marchent correctement.

Etape 5

Si vous n'avez pas encore utilisé l'héritage lors de l'étape 3, il est fort probable que vous ayez créé des méthodes très similaires dans les deux classes XYRobot et TurtleBot. Ces deux classes représentent tous les deux un robot ayant une mémoire contenant les actions déjà effectuées ainsi qu'une méthode pour défaire ces actions. Une bonne manière d'éviter le code redondant (recopié/dupliqué dans les deux classes) est de le mettre dans un classe mère Robot commune à ces deux classes, et de déclarer XYRobot et TurtleBot comme des classes filles de cette classe. En particulier, la classe mère peut contenir tout le code pour retenir les actions effectuées et de les refaire dans le sens inverse.

Lors de cet étape on vous demande donc de :

  • Créer une nouvelle classe mère Robot qui doit se trouver dans un fichier Robot.py.
  • Réécrivez les classes XYRobot et TurtleBot comme sous-classes de Robot.
  • Bouger le code qui est commun entre les classes filles XYRobot et TurtleBot vers la classe mère Robot.

Etape 6

Rejouez les tests unitaires de l'étape 4 sur les nouvelle classes XYRobot et TurtleBot qui héritent de la nouvelle clases mère. Si tout va bien ces tests doivent toujours fonctionner. Si ce n'est pas le cas, adapter le code ou les tests où nécessaire.

Remise de votre solution

  1. Un fichier Robot.py qui contient la classe mère Robot avec le code de gestion de l'historique.

    (Si vous n'êtes pas arrivé à l'étape 5, remettez simplement un fichier vide ici.)

  2. Un fichier XYRobot.py qui contient la classe XYRobot qui hérite de Robot.

    (Si vous n'êtes pas arrivé à l'étape 5, remettez la version de votre XYRobot sans héritage ici.)

  3. Un fichier TurtleBot.py qui contient la classe TurtleBot qui hérite de Robot.

    (Si vous n'êtes pas arrivé à l'étape 5, remettez la version de votre TurtleBot sans héritage ici.)

  4. Un fichier TestXYRobot.py qui contient les tests unitaires pour votre classe XYRobot.

    Ce fichier TestXYRobot.py doit charger le fichier XYRobot.py et exécuter les différents tests unitaires sur la classe XYRobot.

  5. Un fichier TestTurtleBot.py qui contient les tests unitaires pour votre classe TurtleBot.

    Ce fichier TestTurtleBot.py doit charger le fichier TurtleBot.py et exécuter les différents tests unitaires sur la classe TurtleBot.


        
        
<string>

Table des matières

Mission 11 - Listes chainées

Mission 11 : Les listes chaînées

Mission 11 : Les listes chaînées

/syllabus/info1-exercises/assets/linkedtrain.jpg

Introduction

Le Service des sports fait appel à vous dans le cadre du développement d'un système informatique pour la gestion des résultats de la course des 24h Vélo de Louvain-la-Neuve. Vous devez réaliser un programme Python permettant d'encoder et de manipuler les différentes données nécessaires (coureurs, résultats, classements) pour tenir à jour les classements en cours de course. Par exemple, il faut pouvoir afficher en temps réel, sur des écrans disposés dans la ville et sur internet, le classement des meilleurs temps pour le tour du circuit.

Un classement par temps (Tour de France 2013)

Pour cette mission, vous allez devoir implémenter une classe Python permettant de manipuler un tel classement et de le mettre à jour avec de nouveaux résultats. Le classement doit être ordonné, et on ne désire pas refaire le tri pour chaque nouveau résultat: il faut donc une structure de liste ordonnée avec des opérations qui maintiennent cet ordre. Vous utiliserez pour cela une liste chaînée.

Objectifs

A l'issue de cette mission, chacun d'entre vous :

  • sera en mesure d'exploiter les listes chaînées
  • aura eu l'occasion d'écrire des tests unitaires pour ses programmes

Préparation, étude et apprentissage

Comment bien préparer la mission?

  1. Assister au cours pour comprendre les notions et concepts qui seront abordés.

  2. Lire attentivement et comprendre le chapitre sur Linked lists dans la partie Objects du syllabus théorie :

    • 6 - Linked lists
      • Embedded references
      • The Node class
      • Linked lists as collections
      • Linked lists and recursion
      • Infinite lists
      • Ambiguity between lists and nodes
      • Modifying lists
      • Wrappers and helpers
      • The LinkedList class
  3. Essayer et comprendre le code développé dans le syllabus et qui se trouve aussi dans l'annexe correspondant :

  4. Dans cette mission, vous devrez de nouveau utiliser le framework unittest pour produire des classes de test. Relisez la documentation si vous aviez du mal à créer des tests unitaires lors de la mission précédente.

  5. Répondre aux questions à choix multiples et aux autres questions de démarrage.

Questionnaire de démarrage

Ces questions supposent que vous avez lu le chapitre du syllabus théorique sur les listes chaînées et que vous avez lu et testé le code des classes LinkedList et Node dans l'annexe code des classes LinkedList et Node dans l'annexe correspondante.

Questions à choix multiples

Les questions à choix multiples de cette mission sont accessibles en ligne depuis https://inginious.info.ucl.ac.be/course/LSINF1101-PYTHON/Session11_QCM.


        
        

Questions ouvertes

0. Notions théoriques

Répondez aux questions suivantes:

  1. Qu'est-ce qu'une liste chaînée?
  2. Qu'est-ce qu'une structure de données? Donnez un exemple.
  3. Expliquez la différence entre un noeud (Node) et sa cargaison (cargo).
  4. Expliquez la différence entre un noeud (Node) et une liste chainée (LinkedList).
  5. Dans le livre et le cours magistral, la notion d'une liste chaînée a d'abord été expliqué sans la classe LinkedList, simplement en chaînant des noeuds l'un à l'autre. Pourquoi avons nous alors besoin d'une classe LinkedList?
  6. Expliquez et donnez un exemple du caractère ambiguë d'un noeud.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

1. Utilisation d'une LinkedList

Question 1a : Manipuler une liste vide

Cette question concerne l'utilisation de la classe LinkedList dont le code se trouve dans l'annexe suivante du syllabus : Appendix - Source code of linked lists

Utilisez la classe LinkedList pour créer une liste chaînée vide l.

 
 

Donnez une instruction Python pour imprimer le contenu de cette liste l.

 
 

Quel résultat sera affiché si on essaie d'imprimer la liste l avec l'instruction print(l)? Expliquez.

 
 

Donnez une instruction Python pour imprimer la taille de cette liste l .

 
 

Quel résultat sera affiché si on essaie d'imprimer la taille avec l'instruction print(l.__length)? Expliquez.

 
 

Question 1b : Remplir la liste

Pour ajouter des éléments à une liste vide, quelqu'un a écrit les instructions suivantes:

l = LinkedList()
l.add(3)
l.add(2)
l.add(1)
l.print()

Mais quelqu'un d'autre à écrit:

l = LinkedList()
l.add(Node(3))
l.add(Node(2))
l.add(Node(1))
l.print()

Dans les deux cas, exécuter l'instruction l.print() affichera [ 1 2 3 ].

Lequel des deux fragments de code ci-dessus vous semble correct? Pourquoi l'autre imprime quand-même le même résulat?

Indice : qu'est-ce qui se passe si on exécute l'instruction print(Node(Node(Node(1))))? Pouvez-vous expliquer?

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

2. Imprimer une liste chaînée

Dans le code donné dans l'annexe Appendix - Source code of linked lists la méthode print() de la classe LinkedList imprime les éléments d'une liste chaînée, séparés par une espace. Par exemple:

>>> l = LinkedList()
>>> l.add(3)
>>> l.add(2)
>>> l.add(1)

>>> l.print()
[ 1 2 3 ]

Or, normalement la convention est de séparer les éléments d'une liste par des virgules. Ajoutez une nouvelle méthode print_avec_virgule() dans la classe LinkedList qui imprime la liste comme suite:

>>> l.print_avec_virgule()
[ 1, 2, 3 ]

Attention: il n'y a pas de virgule après le dernier élément ou si on imprime une liste vide.

>>> l = LinkedList()
>>> l.print_avec_virgule()
[ ]

3. Imprimer une liste chaînée (généralisation)

Généralisez la méthode print_avec_virgule de l'exercice précédente par une méthode print_avec_separateur qui peut prendre n'importe quelle séparateur. Par exemple:

>>> l = LinkedList()
>>> l.add(3)
>>> l.add(2)
>>> l.add(1)

>>> l.print_avec_separateur(", ")
[ 1, 2, 3 ]
>>> l.print_avec_separateur(" ")
[ 1 2 3 ]
>>> l.print_avec_separateur(" - ")
[ 1 - 2 - 3 ]

4. Initialiser une liste chaînée

Changez la méthode d'initialisation __init__ de la classe LinkedList (cf. l'annexe Appendix - Source code of linked lists ), afin qu'elle prenne une liste Python comme paramètre pour initialiser la liste chaînée. Attention: le premier élément de cette liste doit devenir le premier élément de la liste chainée, et ainsi de suite. Spécifier bien cette nouvelle méthode __init__ avec des conditions pre et post.

def __init__(self, lst=[]):
    self.__length = 0
    self.__head = None
    # initialiser la liste chainêé avec les éléments de lst ...

Par exemple:

>>> l = LinkedList([1,2,3])
>>> l.print()
[ 1 2 3 ]

>>> l = LinkedList([])
>>> l.print()
[ ]


        
        

5. Supprimer un élément

Ajoutez à la classe LinkedList (cf. l'annexe Appendix - Source code of linked lists ) une méthode remove() pour supprimer le noeud en tête de la liste. Si la liste chaînée était déjà vide, cette méthode ne fait rien.

l = LinkedList()
l.add(3)
l.add(2)
l.add(1)
l.print()
# [ 1 2 3 ]
l.remove()
l.print()
# [ 2 3 ]
l.remove()
print(l.length)
# 1
l.remove()
l.print()
# [ ]
l.remove()
l.print()
# [ ]


        
        

6. Ajouter un élément à la fin

Ajoutez à la classe LinkedList une méthode add_to_end(self, cargo) pour ajouter un noeud en queue de la liste. Ajoutez d'abord un nouvel attribut last dans la méthode d'initialisation pour garder une référence vers ce dernier élément. Changez ensuite la méthode add pour assigner cette référence lors de l'ajout du premier noeud, et la méthode remove pour remettre cette référence à None lorsque la liste est de nouveau vide. Implémentez ensuite la méthode add_to_end.

7. Supprimer un élément à la fin

Est-ce facile d'implémenter une méthode remove_from_end(self, cargo) pour supprimer un élément de la queue d'une LinkedList? Expliquez en mots comment vous feriez.

 
 
 
 
 
 
 
 
 
 

8. Imbriquer une classe dans une autre

En Python il est possible d'imbriquer une classe dans une autre. Ceci peut être utile si une classe n'est utilise que par une seule autre classe. C'est le cas ici avec la classe Node. Ce n'est qu'une classe auxiliaire pour la classe LinkedList. Adapter le code de votre classe LinkedList en imbriquant le code de la classe Node dedans. Il suffit d'indenter le code de la classe Node et de changer chaque référence à la classe Node à l'interieur de la classe LinkedList par self.Node.

Quels sont les avantages et désavantages d'imbriquer la classe Node dans la classe LinkedList?

 
 
 
 
 
 
 
 
 
 

9. Insérer un élément

Considérez qu'on désire utiliser la classe LinkedList pour stocker une liste de strings. On souhaite qu'elle soit en permanence en ordre alphabétique croissant.

  1. Dessinez complètement une telle liste chainée contenant les trois strings "abc", "def" et "xyz".

     
     
     
     
     
     
     
     
  2. Expliquez en français et dessinez les opérations à effectuer pour ajouter un Node contenant "aaa" dans cette liste.

     
     
     
     
     
     
     
     
  3. Même question avec un Node contenant "ghi".

     
     
     
     
     
     
     
     
  4. Même question avec un Node contenant "def".

     
     
     
     
     
     
     
     
  5. Même question avec un Node contenant "zzz".

     
     
     
     
     
     
     
     

Comment feriez-vous pour écrire dans la classe LinkedList cf. l'annexe Appendix - Source code of linked lists ) la méthode insert dont la spécification est la suivante:

def insert(self,s):
"""
@pre  s est un string à insérer dans la liste chainée;
      self est une liste chaînée dont les noeuds contiennent des strings;
      les noeuds de la liste sont ordonnées en ordre croissant selon les valeurs (strings)
      qu'ils contiennent.
@post Un noeud contenant le String s a été inséré dans la liste de façon
      à ce qu'après l'insertion celle-ci soit toujours en ordre croissant.
"""

Pour rappel, en Python on peut facilement comparer un string avec un autre avec l'opérateur <. Vous pouvez aussi considérez que la classe Node se trouve maintenant à l'intérieur de la classe LinkedList et possède la méthode mutateur suivante:

class Node:

    def set_next(self,node):
        self.__next = node


        
        

10. Tests unitaires

Lors de la mission précédente, les tests unitaires avec unittest ont été introduits. Pour revisiter ce concept, consultez éventuellement un tutoriel online comme https://openclassrooms.com/fr/courses/235344-apprenez-a-programmer-en-python/2235416-les-tests-unitaires-avec-unittest

Créez un test unitaire pour tester la classe LinkedList. Ce test doit contenir des tests pour les différentes méthodes de la classe LinkedList comme size(), first() ou add(valeur) (code original), remove() (question 5), add_to_end(valeur) (question 6) ou insert(valeur) (question 9). Pour vous mettre sur le bon chemin, votre classe test aura la structure suivante:

import unittest

class LinkedListTest(unittest.TestCase):
    """Classe de test utilisé pour tester la classe LinkedList"""

    def test_size(self):
        """Test de la methode size() de la classe LinkedList."""
        # ... assert*(...) ...

    def test_first(self):
        """Test de la methode first() de la classe LinkedList."""
        # ... assert*(...) ...

    def test_add(self):
        """Test de la methode add(valeur) de la classe LinkedList."""
        # ... assert*(...) ...

    def test_remove(self):
        """Test de la methode remove() de la classe LinkedList."""
        # ... assert*(...) ...

    def test_add_to_end(self):
        """Test de la methode add_to_end(valeur) de la classe LinkedList."""
        # ... assert*(...) ...

    def test_insert(self):
        """Test de la methode insert(valeur) de la classe LinkedList."""
        # ... assert*(...) ...

if __name__ == '__main__':
    unittest.main()
Mission

Mission

Cette mission est un peu différente des autres missions. En effet, vous trouverez sur INGInious une grande partie du code nécessaire, sauf l'implémentation complète de la classe Classement que vous devez produire, ainsi que la classe OrderedLinkedList qu'elle doit utiliser. Comme source d'inspiration on vous donnera également une implémentation assez complète d'une classe LinkedList sur laquelle vous pouvez vous baser, ou que pouvez étendre, pour implémenter la classe OrderedLinkedList. Vous devez également écrire des classes de test détaillées (ClassementTest et OrderedLinkedListTest) en utilisant le framework de test unittest, pour vérifier votre implémentation de la classe Classement.

L'archive contient déjà une implémentation primitive de la classe Classement à base d'un dictionnaire. Néanmoins, cette implémentation est encore incomplète (elle ne gère pas correctement la position des coureurs dans le classement). Vous devez la remplacer par votre implémentation. Elle permet toutefois au programme de fonctionner. Il suffit d'exécuter la méthode de class main() de la classe Main: le programme simulera à la console l'ajout d'un nouveau résultat aléatoire toute seconde.

Etapes

  1. Lors de la mission, vous allez implémenter un classement de résultats de coureurs, chaque résultat représenté par un coureur (ayant un certain nom et âge) et le temps effectué par ce coureur. Ces résultats doivent être ordonnés dans la liste selon leur temps. Le meilleur résultat (le coureur avec le meilleur temps) se trouve en tête de la liste. Le résultat du coureur le plus lent se trouve en queue de la liste.

  2. Vous allez utiliser une liste chaînée ordonnée (OrderedLinkedList ) pour implémenter un tel classement de coureurs. Réfléchissez aux différentes opérations qu'on peut effectuer sur un classement et comment on devrait les implémenter en utilisant une liste chaînée ordonnée.

  3. Avant de commencez à implémenter cette classe Classement, représentez la structure de votre classement comme liste chaînée ordonnée graphiquement, et montrez ce qu'il se passe lorsque, successivement:

    • Vous créez un classement vide;
    • Vous ajoutez un résultat pour le coureur A en tête de classement (méthode add);
    • Vous ajoutez un résultat pour le coureur B en fin de classement (méthode add);
    • Vous ajoutez un résultat pour le coureur C en milieu de classement (méthode add);
    • Vous recherchez les résultats des coureurs A, B et C (méthode get);
    • Vous recherchez les positions des coureurs A, B et C (méthode getPosition);
    • Vous retirez le résultat du coureur B (méthode remove);
    • Vous retirez le résultat du coureur A (méthode remove);
    • Vous tentez de retirer le résultat d'un coureur D (méthode remove).

  4. Implémentez la classe OrderedLinkedList dont vous aurez besoin pour implémenter votre classe Classement.

  5. Implémentez une classe de test OrderedLinkedListTest pour tester le bon fonctionnement de votre classe OrderedLinkedList.

  6. Sur base de notre implémentation incomplète de la classe Classement au moyen d'une dictionnaire, écrivez un squelette de votre classe Classement au moyen d'une liste chaînée ordonnée, qui remplacera la notre. Respectez bien les pré- et post-conditions des différentes méthodes de la classe :

    class Classement :
    
        def __init__(self):
            """
            @pre: -
            @post: un classement vide de taille 0 a été créé
            """
    
        def size(self):
            """
            Méthode accesseur.
            Retourne la taille de ce classement.
            @pre:  -
            @post: Le nombre de résultats actuellement stockés dans ce classement a été retourné.
            """
    
        def add(self,r):
            """
            Ajoute un résultat r dans ce classement.
            @pre:  r est une instance de la classe Resultat
            @post: Le résultat r a été inséré selon l'ordre du classement.
                   En cas d'ex-aequo, r est inséré après les autres résultats de même ordre.
            """
    
        def get(self,c):
            """
            Retourne le résultat d'un coureur donné.
            @pre c est un Coureur
            @post retourne le premier (meilleur) Resultat r du coureur c dans le
                  classement. Retourne None si le coureur ne figure pas (encore)
                  dans le classement.
            """
    
        def get_position(self,c):
            """
            Retourne la meilleure position d'un coureur dans ce classement.
            @pre c est un Coureur
            @post retourne un entier représentant la position du coureur c dans ce classement,
                  à partir de 1 pour la tête de ce classement. Si le coureur figure plusieurs fois
                  dans le classement, la première (meilleure) position est retournée.
                  Retourne -1 si le coureur ne figure pas dans le classement.
            """
    
        def remove(self,c):
            """
            Retire un résultat du classement.
            @pre  c est un Coureur
            @post retire le premier (meilleur) résultat pour le coureur c du classement.
                  c est comparé au sens de __eq__. Retourne c si un résultat a été retiré,
                  of False si c n'est pas trouvé dans la liste.
            """
    
        def __str__(self):
            """
            Méthode magique
            Retourne une représentation string de cet objet.
            @pre:  -
            @post: Retourne une représentation de ce classement sous forme d'un string,
                   avec une ligne par résultat.
            """
    
  7. Ecrivez une classe de test ClassementTest, la plus complète possible permettant de vérifier le bon fonctionnement de votre implémentation de la classe Classement. Utilisez pour cela les méthodes comme assertEqual ou d'autres méthodes définies dans unittest.

  8. Pensez à découper votre classe de test en plusieurs méthodes. Cela facilitera la visualisation des résultats des tests. Vous trouverez sur la tâche INGInious un exemple de classe de test CoureurTest pour la classe Coureur.

  9. Justifiez vos tests dans le fichier README.TXT.

  10. Remplacer votre classe Classement par celle qui vous est fourni et exécutez la méthode main() de la classe Main. Observez que vos classements sont correctement mis à jour. Félicitations!

  11. N'oubliez pas de soumettre votre implémentation des classes OrderedLinkedList et Classement, vos classes de test OrderedLinkedListTest et ClassementTest et votre fichier README.TXT à votre tuteur.

Remise de votre solution

Pour cette mission, vous devez soumettre au serveur de soumissions de programmes du cours, vos classes OrderedLinkedList et Classement dans des fichier orderedlinkedlist.py et classement.py, vos classes test OrderedLinkedListTest et ClassementTest dans des fichiers orderedlinkedlisttest.py et classementtest.py, ainsi que votre fichier README.txt.