<string>

<string>

Table des matières

Mission 8 - Classes et objets

Mission 8 : Classes et objets

Mission 8 : Classes et objets

1   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.

2   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;
    • méthode d'initialisation et constructeur;
  • pourra faire des manipulations simple sur un objet comme :
    • la création d'un objet;
    • accéder à un de ces attributs;
    • modifier la valeur d'un attribut;
    • appeler une méthode sur cet objet;
  • aura appris la conversion automatique d'un objet en un string par la méthode magique __str__;
  • aura écrit un premier programme utilisant des objets en Python;

3   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:

  • 1 - Classes and objects - Basics
    • Object-oriented programming
    • User-defined compound data types
    • Attributes
    • Improving the initialiser
    • Adding other methods to the class
    • Instances as arguments and parameters
    • Converting an instance to a string
    • Instances as return values
    • A change of perspective
    • Objects can have state
  • 2 - Classes and objects - Advanced
    • Rectangles
    • Objects are mutable
    • Sameness
    • Copying

Un exemple d'un un premier programme utilisant des objets en Python, utlisé comme illustration dans le cours théorique, se trouve dans l'annexe suivant:

4   Questionnaire de démarrage

4.1   Questions à choix multiple

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


        
        

4.2   Questions ouvertes

4.2.1   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 de la moyenne des scores obtenus par l'étudiant sur les 3 tests.
        @pre: les variables d'instance test1, test2 et test3
              contiennent des valeurs de type int
        @post: retourne la 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("La moyenne de vos scores est " + str(student.average_score()))
student2 = student
print("La moyenne de vos scores 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éthode d'instance**
 
 
 
 
**Référence à un objet et la valeur None**
 
 
 
 
**Référence à self**
 
 
 
 

4.2.2   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.

 
 
 
 
 
 
 
 

4.2.3   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

4.2.4   Création de classes


        
        

4.2.5   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)

Remarque: La notation x=None, y=None dans l'entête de la méthode __init__ permet de donner des valeurs par défaut à certains ou à tous paramètres de la fonction. Lorsque nous appelons la fonction, si des valeurs ne sont pas données pour ces paramètres, ils reçoivent les valeurs par défaut sinon, on peut leur donner une valeur différente en donnant un argument lors de l'appel.

Considérons maintenant 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)        ##8##
print(p1)        ##9##
print(p2)        ##10##
 
 
 

4.2.6   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? Pourquoi? Expliquez ce comportement avec un dessin.

 
 
 
 

4.2.7   Une méthode simple


        
        

4.2.8   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.


        
        

Mission 8

Mission 8

1   Introduction

L'objectif principal de cette mission est de définir et d'utiliser trois classes correspondant respectivement à une durée (une espace de temps sous la forme heures-minutes-secondes), à une chanson (caractérisée par un titre, un auteur et une durée) et à un album (un ensemble de chansons). 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, et à la durée de la chanson en minutes et secondes, 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, pendant qu'un étudiant se concentre sur le développement d'une classe, l'autre pourrait préparer les tests pour cette classe. Les spécifications des méthodes étant donné par avance, il est en effet 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?

Remarque: Lors de cette mission vous utiliserez encore les instructions 'assert' pour tester votre programme orienté objet. Dans une mission futur nous introduirons le mécanisme des 'tests unitaires' qui sera encore mieux approprié pour tester du code orienté objets.

2   Etapes

2.1   Fichiers

Chargez les fichiers "music-db.txt", "mission8.py" et "test.py".

  • "music-db.txt" contient une liste de plus de 300 chansons, au format décrit ci-dessus.
  • "mission8.py" contient la définition des 3 classes Duree, Chanson et Album à compléter, mais avec une implémentation vide.
  • "test.py" contient une ébauche (à compléter par vous) d'un ensemble de fonctions test pour tester le bon comportement des 3 classes et du programme à réaliser.

2.2   La classe Duree

Commencez par définir la classe Duree qui représente une durée exprimée en heures (h), minutes (m) et secondes (s).

Ajoutez d'abord la méthode d'initialisation __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. Ajoutez une spécification à cette méthode.

def __init__(self,h,m,s):
    """
    @pre: h, m et s sont des entiers positifs (ou zéro)
          m et s sont < 60
    @post: Crée une nouvelle durée en heures, minutes et secondes.
    """

Ajoutez ensuite les méthodes correspondant aux spécifications suivantes:

def to_secondes(self):
    """
    @pre:  -
    @post: Retourne le nombre total de secondes de cette instance de Duree (self).
    Par exemple, une durée de 8h 41m 25s compte 31285 secondes.
    """

def delta(self,d) :
    """
    @pre:  d est une instance de la classe Duree
    @post: Retourne la différence en secondes entre cette durée (self)
           et la durée d passée en paramètre.
           Cette valeur renovoyée est positif si cette durée (self)
           est plus grand que la durée d, négatif sinon.
    Par exemple, si cette durée (self) est 8h 41m 25s (donc 31285 secondes)
    et la durée d est 0h 1m 25s, la valeur retournée est 31200.
    Inversement, si cette durée (self) est 0h 1m 25s et la durée
    d est 8h 41m 25s, alors la valeur retournée est -31200.
    """

def apres(self,d):
    """
    @pre:  d est une instance de la classe Duree
    @post: Retourne True si cette durée (self) est plus grand que la durée
           d passée en paramètre; retourne False sinon.
    """

def ajouter(self,d):
    """
    @pre:  d est une instance de la classe Duree
    @post: Ajoute une autre durée d à cette durée (self),
           corrigée 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).
           Ne retourne pas une nouvelle durée mais modifié la durée self.
    """

def __str__(self):
    """
    @pre:  -
    @post: Retourne cette durée sous la forme de texte "heures:minutes:secondes".
    Astuce: l'expression "{: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.
    """

2.3   Tester la classe Duree

Dans le fichier test.py, ajoutez ou complétez au fur et à mesure des fonctions tests pour tester la classe Duree. Exécutez ces tests régulièrement et assurez-vous que les résultats sont conformes aux spécifications, en corrigeant au besoin.

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, pour la méthode ajouter() de votre classe Duree, vous pourrez implémenter une fonction test_Duree_ajouter() pour tester les différents scénarios d'utilisation de la méthode ajouter() de cette classe. Vous pouvez vous inspirer des quelques tests qui se trouvent déjà dans le fichier test.py.

2.4   La classe Chanson

Définissez maintenant la classe Chanson représentant une chanson, caractérisée par un titre t (string), un auteur a (string) et une durée d (Duree). Implémentez la méthode d'initialisation __init__ ainsi que la méthode de conversion __str__ correspondant aux spécifications suivantes:

 def __init__(self,t,a,d):
     """
     @pre:  t et a sont des string, d est une instance de la classe Duree
     @post: Crée une nouvelle chanson, caractérisée par un titre t,
            un auteur a et une durée d.
     """

def __str__(self):
    """
    @pre:  -
    @post: Retourne un string décrivant cette chanson sous le format
           "TITRE - AUTEUR - DUREE".
           Par exemple: "Let's_Dance - David_Bowie - 00:04:05"
    """

2.5   Tester la classe Chanson

Ajoutez au fichier test.py une série de tests pour tester les différentes méthodes de la classe Chanson.

2.6   La classe Album

Définissez maintenant la classe Album représentant un album contenant une ou plusieurs chansons.

Implémentez d'abord la méthode d'initialisation __init__ pour créer un album vide:

def __init__(self, numero):
    """
    @pre:  numero est un entier identifiant de facon unique cet album
    @post: Crée un nouvel album, avec comme identifiant le numero,
           et avec une liste de chansons vide.
    """

Implémentez également une méthode add(self,chanson) pour ajouter une chanson à un album. Cette méthode retourne False et ne modifie rien si lors de l'ajout de la chanson l'album aurait atteint 100 chansons ou sa durée aurait dépassé 75 minutes. Sinon la chanson est rajoutée à la fin de la liste des chansons de cet album, la durée totale de l'album est augmentée avec la durée de la chanson, et la méthode add retourne True.

Finalement, implémentez la 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

2.7   Tester la classe Album

Ajoutez au fichier test.py une série de tests pour tester la classe Album.

2.8   Le programme

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 avec identifiant le numéro 1;
  4. lorsque le nombre de chansons stockés dans cet 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-dessus;
  5. poursuit la lecture du fichier et l'ajout des chansons dans un album suivant (incrémentez le numéro identifiant l'album de 1 pour chaque nouvel album);
  6. répète ces étapes jusqu'à ce que le fichier est 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

2.9   Tester le programme

Complétez le fichier test.py avec quelques tests pour démontrer que vos 3 classes Duree, Chanson et Album collaborent correctement et peuvent être utilisés pour créer des albums de chansons.

3   Diagramme de classes

Le diagramme de classes suivant résume les différentes classes et méthodes à implémenter lors de cette mission :

/syllabus/info1-exercises/assets/class_diagram_mission8.png

4   Remise de votre solution

Pour cette mission, vous devez soumettre votre programme mission8.py, le 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 contenir une définition des 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

1   SMS Store


        
        

2   Création d'objets depuis un fichier


        
        

3   Kill an Enemy


        
        

4   Labyrinth


        
        

5   Personne

Créer une classe Personne décrivant des personnes physiques ayant un nom (un string), un attribut booléen 'desire_enfant' et une liste d'enfants (d'autres instances de la même classe) et comme méthodes:

  • les méthodes magiques __init__ et __str__ ;
  • avoir_enfant_avec(autre) crée une nouvelle personne avec un nom inconnu (None), à condition que la personne même et l'autre personne désirent un enfant ;
  • une méthode auxiliaire ajoute_enfant(bebe) utilisée par avoir_enfant_avec(autre) pour ajouter un enfant à la liste des enfants de chacun des deux parents ;
  • renommer(nom) donne un nouveau nom à la personne ;
  • premier_ne() retourne le premier enfant de la personne.

Utilisez et testez cette classe pour créer une grande famille de personnes.

<string>

Table des matières

Mission 9 - Héritage

Mission 9 : Héritage

Mission 9 : Héritage

1   Introduction

Dans cette mission vous allez étendre votre logiciel de lecture de musique (Mission 8), en utilisant le concept de l'héritage, pour pouvoir traiter d'autres types de médias que les chansons, comme les vidéos, livres audio, etc.

2   Objectifs

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

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

  • aura appris la notion de variable de classe et son utilité;

  • 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;
    * l'utilisation de ``self`` et ``super()``;
    
  • sera capable d'utiliser l'héritage pour étendre un programme Python;

  • 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.
    

3   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:

4   Questionnaire de démarrage

4.1   Questions à choix multiple

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


        
        

4.2   Questions ouvertes

4.2.1   Egalité

Considérez une classe Pair :

class Pair:

    def __init__(self, x, y):
        self.a = x
        self.b = y

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

Maintenant considérez le code suivant :

p1 = Pair(9, 42)
p2 = Pair(9, 42);
print(p1 == p2)

La dernière instruction affichera False. (Pourquoi?)

Ajoutez une méthode __eq__(self, p) à la classe Pair qui compare la valeur de deux paires, de manière à ce que print(p1 == p2) imprimera True au lieu de False.

Dans l'implémentation de votre méthode pensez également à gérer le cas où p == None.


        
        

4.2.2   L'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()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

4.2.3   C'est 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 n 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 m 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()
 
 
 
 
 
 
 

4.2.4   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 est_visible(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,longueur,largeur,x,y) :
        """
        @pre longueur et 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.longueur = longueur
        self.largeur = largeur

    def __str__(self) :
        return str((self.longueur,self.largeur,self.x,self.y,self.est_visible()))

>>> 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()
 
 
 
 
 
 
 
 

  • est_visible()
 
 
 
 

4.2.5   Variables privés


        
        

4.2.6   Carré

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?
 
 
 
 

4.2.7   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)
 
 
 
 

4.2.8   Variable de classe

Et voici finalement une dernière question pour illustrer l'utilisation d'une variable de classe.

Complétez la classe Ticket ci-dessous:

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

    __prochain_numero = 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

1   Description

Suite au succès de votre logiciel de lecture de musique (Mission 8), vous avez décidé d'étendre sa fonctionnalité pour pouvoir lire d'autres types de médias que les chansons (vidéos, livres audio, ...).

Vous disposez pour commencer d'un fichier mission9.py comportant 3 classes:

  1. Duree: représente une durée dans le temps dans le format hh:mm:ss. Usage similaire à la classe du même nom de la Mission 8.
  2. Media: représente un média générique, comme une chanson, un livre audio ou une vidéo qui peut être "joué" pour être écouté, lu ou regardé.
  3. ListeLecture: représente une compilation nommée de médias.

Un fichier de test initial test.py est également fourni. Exécuter ce fichier test affichera l'exemple de liste de lecture suivant:

[#1] Minecraft (4 medias)
01: (00:10:01, Média) 'Tuto installation Minecraft (100% gratuit!!)' par LeCrafteur
02: (00:03:36, Média) 'Sweden' par C418
03: (00:04:24, Média) 'Revenge' par CaptainSparklez
04: (02:36:21, Média) 'Journal d'un noob (tome 1)' par Cube Kid

Vous remarquerez le [#1] au début de l'affichage. Ceci représente l'identifiant unique d'une liste de lecture.

L'objectif est d'étendre la fonctionnalité des Media en créant de nouvelles classes, plus spécialisées, qui en héritent tout en restant utilisables par la classe ListeLecture. L'avantage de cette approche est qu'elle nous permet d'implémenter de nouvelles fonctionnalités pour des cas particuliers de Media, sans pour autant devoir modifier le code de la classe ListeLecture pour supporter celles-ci. C'est un des avantages principaux de la programmation orientée objet.

Votre programme final devrait être capable de générer la playliste suivante:

[#3] Minecraft (4 medias)
01: (00:10:01, Vidéo) 'Tuto installation Minecraft (100% gratuit!!)' par LeCrafteur (720p)
02: (00:03:36, Chanson) 'Sweden' par C418 [Album: Minecraft OST]
03: (00:04:24, Chanson) 'Revenge' par CaptainSparklez (feat. Villageois, Herobrine) [Album: Fallen Kingdoms]
04: (02:36:21, Livre Audio) 'Journal d'un noob (tome 1)' par Cube Kid, édité par 404 Éditions

et l'analyse de tailles de fichiers suivant:

TOTAL : 238.01MB
[120.20MB] Tuto installation Minecraft (100% gratuit!!)
[10.80MB] Sweden
[13.20MB] Revenge
[93.81MB] Journal d'un noob (tome 1)
TOTAL : 238.01MB

2   Étapes

Voici les étapes à suivre.

2.1   Fichiers

Chargez les fichiers mission9.py et test.py et étudiez leurs contenus. Vous disposez des classes et fonctions suivantes:

  • Duree: cette classe représente une durée dans le temps. Elle contient les composantes en heures, minutes et secondes de la durée, une méthode qui calcule cette durée en secondes, ainsi qu'une méthode pour ajouter une durée à une autre.
  • Media: cette classe représente un média générique, ayant un titre, un auteur et une durée. Elle contient également la méthode taille() qui renvoie la taille en méga-octets du média, ainsi qu'une méthode taille_par_seconde(), utilisée par la précédente, qui renvoie la taille en méga-octets par seconde de durée. Cette méthode lève une erreur NotImplementedError par défaut, car elle dépend du type de média et donc devra être réécrite par les classes-filles. Pour finir, elle contient la méthode type_media() qui renvoie un string décrivant le type de média de l'objet. Par défaut ce type sera "Média".
  • ListeLecture: cette classe représente une compilation de Media. Elle a un identifiant unique, un nom, une méthode ajouter(media) pour ajouter un média à la liste de lecture, et une méthode __str__ qui imprime les informations de la playliste de manière formattée.
  • afficher_playliste: une fonction de test de la classe ListeLecture qui imprime la liste de lecture de l'exemple repris ci-dessus.

Vous devrez vous-mêmes créer au moins les deux classes Video et Chanson qui héritent de la classe Media, ainsi que des tests supplémentaires pour tester le bon fonctionnement des nouvelles fonctionnalités. Une classe LivreAudio est fournie pour servir d'exemple.

2.2   La classe LivreAudio

Cette classe représente un livre audio, et est une classe-fille de Media. Dans la méthode __init__, elle prend un argument en plus que celle de Media: editeur représente l'éditeur du livre associé.

La méthode taille_par_seconde() est remplacée par une nouvelle version qui renvoie 0.01, qui est la taille de lecture d'un livre audio par défaut en MB/sec. Observez comment la méthode taille() dans Media fera appel à cette version de la méthode taille_par_seconde(), et non à celle définie au départ dans Media [1]. De même, la méthode type_media() renvoie ici "Livre Audio" au lieu de "Média", et ce changement prend effet quand la méthode __str__() d'un média est appelé.

class LivreAudio(Media):
    """
    Représente un livre audio. En plus des attributs présents dans 'Media',
    'LivreAudio' inclu aussi un attribut représentant l'éditeur du livre.
    """

    def __init__(self, titre, auteur, duree, editeur):
        """
        @pre:  titre est un string
               auteur est un string
               duree est une instance de 'Duree'
               editeur est un string
        @post: un livre audio ayant les propriétés demandées
        """
        super().__init__(titre, auteur, duree)
        self.editeur = editeur

    def taille_par_seconde(self):
        """
        Renvoie la taille de lecture par défaut d'un livre audio
        en méga-octets par seconde
        """
        return 0.01

    def type_media(self):
        """
        Renvoie un string indiquant le type de média
        """
        return "Livre Audio"

    def __str__(self):
        s = super().__str__() + ", édité par " + self.editeur
        return s

Pour une playliste qui ne contient qu'un seul média de type LivreAudio, votre programme pourrait afficher un output comme:

[#2] Livres audio Minecraft (1 medias)
01: (02:36:21, Livre Audio) 'Journal d'un noob (tome 1)' par Cube Kid, édité par 404 Éditions

2.3   La classe Video

  • Au fichier mission9.py, ajoutez une nouvelle classe Video qui hérite de la classe Media. Cette nouvelle classe représente une vidéo que l'on pourrait visionner sur notre logiciel de lecture multimédia.

  • Définissez une méthode d'initialisation avec le titre, l'auteur, la durée et la résolution en paramètre. La résolution est un string qui ne peut prendre que quelques valeurs spécifiques, à savoir '720p', '1080p' ou '4K' (dans l'ordre de fidélité croissant). Une valeur non valable pour la résolution doit lever une ValueError.

  • Re-définissez la méthode taille_par_seconde() afin que chaque seconde de vidéo pèse 0.1 Mo, multiplié par 2, 5 ou 10 selon la résolution choisie.

  • Re-définissez la méthode __str__ afin d'ajouter des informations sur la résolution de la vidéo, en plus des informations déjà fournies dans la même méthode de classe-mère.

  • Dans le fichier test.py, ajoutez des tests pour tester cette nouvelle classe et son utilisation dans une playliste. Par exemple, vérifiez que la ligne correspondant à une vidéo soit affichée comme:

    01: (00:10:01, Vidéo) 'Tuto installation Minecraft (100% gratuit!!)' par LeCrafteur (720p)
    

2.4   La classe Chanson

  • Créez également une nouvelle classe Chanson qui hérite de la classe Media. Cette nouvelle classe représente une chanson, qui peut appartenir à un album et peut avoir d'autres artistes que l'auteur en featuring.

  • Définissez une méthode d'initialisation avec le titre, l'auteur, la durée, l'album (string) et les autres artistes featuring avec l'auteur (liste de strings). L'album a une valeur par défaut de None, et les featuring une liste vide.

  • Re-définissez la méthode taille_par_seconde() afin que chaque seconde pèse 0.05 Mo.

  • Re-définissez la méthode __str__ afin d'ajouter des informations sur l'album et les featurings de la chanson s'il y en a.

  • Dans le fichier test.py, ajoutez des tests pour tester cette nouvelle classe et son utilisation dans une liste de lecture. Par exemple, vérifiez que la ligne correspondant à une vidéo soit affichée comme:

    02: (00:03:36, Chanson) 'Sweden' par C418 [Album: Minecraft OST]
    03: (00:04:24, Chanson) 'Revenge' par CaptainSparklez (feat. Villageois, Herobrine) [Album: Fallen Kingdoms]
    

2.5   Identifiant des listes de lecture

Puisque l'identifiant d'une liste de lecture doit être unique, assurez bien lors de la création d'une instance de ListeLecture que l'identifiant est incrémenté à chaque fois par rapport à la dernière instance créée, ce qui veut dire que la toute première instance de ListeLecture aura comme valeur pour l'attribut id 1, la deuxième 2, la troisième 3 et ainsi de suite, de sorte que chaque instance ait un identifiant unique.

3   Remise de votre solution

Pour cette mission, vous devez soumettre toutes les classes de votre programme dans un seul 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 Duree, ListeLecture, Media, Video et Chanson.

Votre fichier test.py doit contenir des tests pour chacune des classes et étappes de cette mission, 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 liste de lecture et une analyse de tailles de fichier comme illustré plus haut dans ce document.


        
        

Footnotes

[1]Ce mécanisme où une méthode définie dans une classe mère peut appeler une méthode implémentée par une classe fille, sera vu plus en détail plus tard.
Questions complémentaires

Questions complémentaires

1   Variables d'instance

Quelqu'un a programmé une classe Compte représentant un compte bancaire avec 2 attributs privés __titulaire (représentant le titulaire du compte) et __solde (représentant le montant sur le compte, initialement zéro) et 1 attribut publique représentant la banque du compte.

class Compte :

    def __init__(self, banque, titulaire, solde = 0) :
        self.banque = banque
        self.__titulaire = titulaire
        self.__solde = solde

    def banque(self) :
        return self.banque

    def titulaire(self):
        return self.__titulaire

    def solde(self):
        return self.__solde

    def __str__(self) :
        return "Banque: " + self.banque() \
           + " Compte: " + self.titulaire() \
           + " Solde: " + str(self.solde())

a = Compte("ShittyBank","Kim")
print(a)

Malheureusement, quand on exécute l'instruction print(a), une erreur se produit:

Traceback (most recent call last):
  > print(a)
  > print(self.banque())
  > TypeError: 'str' object is not callable

Quel est le problème? Pouvez-vous corriger le code?

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

2   Variable de classe


        
        

3   Questions supplémentaires


        
        
        
        


        
        
        
        


        
        
<string>

Table des matières

Mission 10 - Polymorphisme

Mission 10

Mission 10

1   Introduction

1.1   Objectifs

Dans cette mission, vous allez approfondir votre compréhension des concepts fondamentaux de la programmation orientée objet en Python. L’objectif est de consolider les compétences acquises dans les missions précédentes tout en explorant de nouvelles notions clés :

  • Conception de classes et d’objets : apprendre à définir des classes, créer des instances et gérer leurs attributs et méthodes.
  • Héritage : comprendre comment factoriser le code commun dans une classe mère et, si nécessaire, étendre ou spécialiser le comportement dans les sous-classes.
  • Redéfinition de méthodes : maîtriser la surcharge et la personnalisation des méthodes pour adapter leur comportement selon le type d’objet.
  • Polymorphisme : découvrir comment invoquer des méthodes de manière uniforme tout en laissant chaque sous-classe gérer ses spécificités.
  • Liaison dynamique : comprendre le mécanisme qui permet à Python de déterminer, au moment de l’exécution, quelle méthode appeler.
  • Utilisation de ``self`` et ``super()`` : savoir combiner le comportement générique de la classe mère avec les fonctionnalités propres aux sous-classes.

1.2   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 :

2   Questionnaire de démarrage

2.1   Questions à choix multiple


        
        

2.2   Questions ouvertes

2.2.1   Cartes Bancaires


        
        

2.2.2   Centre d'expédition d'Amazon


        
        

Mission 10 : Polymorphisme

Mission 10 : Polymorphisme

1   Description et objectifs

Dans la mission précédente, vous avez créé et utilisé des classes représentant différents types de médias (LivreAudio, Vidéo, ...), qui sont des sous-classes de la classe Media, ainsi qu’une classe ListeLecture pour les organiser. Dans cette nouvelle mission, vous allez implémenter la fonctionnalité de sauvegarde de vos médias et de vos listes de lecture dans des fichiers texte. Vous travaillerez à la fois sur l’héritage et la réutilisation du code dans la classe mère, tout en approfondissant le concept de polymorphisme : une même méthode pouvant être appelée sur des objets de types différents et produisant un comportement spécifique à chaque type d’objet.

2   Rappel des classes

Vous disposez déjà des classes suivantes :

  • Duree : représente une durée sous le format hh:mm:ss.
  • Media : classe mère représentant un média générique. Elle contient les attributs titre, auteur, duree ainsi que les méthodes taille(), taille_par_seconde() et type_media().
  • LivreAudio : sous-classe de Media. Elle ajoute l’attribut editeur. La méthode save() (permettant de sauvegarder ce type de média) y est déjà implémentée comme exemple.
  • ListeLecture : représente une playlist nommée contenant plusieurs médias.
  • Video : sous-classe de Media. Elle ajoute l’attribut supplémentaire resolution.

Votre fichier mission10.py doit inclure :

  • Les classes ci-dessus, toutes modifiées pour intégrer la méthode save().

3   La méthode save()

La méthode save() doit permettre de sauvegarder les informations d’un média dans un fichier texte. Le fichier doit être nommé selon le format : auteur_titre_typeMedia.txt et contenir les noms des attributs du média ainsi que leurs valeurs.

Exemple attendu pour LivreAudio:

Nom du fichier : "Cube Kid_Journal d'un noob (tome 1)_LivreAudio.txt"

Contenu du fichier:
    Titre : Journal d'un noob (tome 1)
    Auteur : Cube Kid
    Durée : 02:36:21
    Editeur : 404 Éditions

Exemple attendu pour Video :

Nom du fichier : "LeCrafteur_Tuto installation Minecraft_Video.txt"

Contenu du fichier:
    Titre : Tuto installation Minecraft
    Auteur : LeCrafteur
    Durée : 00:10:01
    Resolution : 720p

Vous devez également implémenter la méthode save() dans la classe ListeLecture. Cette méthode doit sauvegarder tous les médias contenus dans la liste, en appelant leur propre méthode save() un par un.

Enfin, dans tous les cas, si un fichier portant le même nom existe déjà, la sauvegarde de ce fichier ne se fera pas.

4   Étapes à suivre

  1. Prenez connaissance du fichier mission10.py et examinez la méthode save() déjà implémentée dans la classe LivreAudio.

  2. Ajoutez la méthode save() également dans la classe Video.

  3. Analysez en détail ce que les méthodes save() des classes LivreAudio et Video ont en commun.

  4. Utilisez le concept d'héritage pour déplacer cette partie commune dans une méthode save() la classe mère Media, afin d'éviter la duplication de code des méthodes save() dans les sous-classes. Astuce :
    • Dans la méthode save() de la classe mère, crééz le fichier et sauvegardez les attributs communs à tous les médias.

    • Dans chaque sous-classe, rouvrez le fichier pour y ajouter les atttributs spécifiques à ce type de média.

    • Pour rouvrir un fichier sans écraser son contenu, utilisez :

      open(nom_fichier, "a")  # mode Append

      plutôt que :

      open(nom_fichier, "w")  # mode Write

      car le mode "w" écrase le contenu existant.

  5. Vérifiez le bon fonctionnement de votre implémentation en créant un fichier test.py qui teste :
    • La création des fichiers.
    • Que les fichiers créés portent les noms corrects.
    • Que le contenu des fichiers correspond aux spécifications.

5   Remise

Pour cette mission, vous devez soumettre les fichiers suivants :

  • mission10.py : vos classes complètes avec la méthode save() ajouté.
  • test.py : script de test montrant que save() fonctionne pour tous les types de médias.
  • README.txt : instructions pour exécuter votre code et tester la sauvegarde.

Bonnes pratiques :

  • Factorisez le code autant que possible pour éviter les duplications.
  • Utilisez les fonctionnalités de l’héritage et super() à bon escient.
  • Nommez vos fichiers et vos variables de manière claire et cohérente.

        
        
Questions complémentaires

Questions complémentaires

Héritage


        
        
<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

1   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.

2   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.

3   Préparation, étude et apprentissage

Comment bien préparer la mission?

  1. Assister au cours et regarder les capsules vidéos 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 :

    • 7 - 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.

4   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.

4.1   Questions à choix multiples

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


        
        

4.2   Questions ouvertes

4.2.1   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.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

4.2.2   Utilisation d'une LinkedList

Question 1 : 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 2 : 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?

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

4.2.3   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()
[ ]

4.2.4   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.2.5   Initialiser une liste chaînée


        
        

4.2.6   Supprimer un élément


        
        

4.2.7   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.

Étapes de l'ajout d'un noeud dans une liste chainée :

  1. Créer un nouveau nœud (new_node) composé de la valeur à ajouter dans la liste.
  2. Parcourir la structure pour identifier le nœud précédant l’emplacement où insérer new_node.
  3. Pour ne pas perdre la référence vers le nœud suivant, faire pointer new_node.next vers le nœud qui suit le nœud courant .
  4. Insérer new_node (cas général, attention aux cas particuliers), en faisant pointer le nœud courant vers new_node.
  5. Mettre à jour les attributs de la classe le cas échéant (taille, tête de liste, etc.).

Pour plus de détails sur les étapes, allez voir le catalogue : Ajout d'un noeud dans une liste chainée .

4.2.8   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.

 
 
 
 
 
 
 
 
 
 

4.2.9   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. Adaptez 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?

 
 
 
 
 
 
 
 
 
 

4.2.10   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".

     
     
     
     
     
     
     
     

        
        

4.2.11   Tests unitaires

Lors de la mission précédente, les tests unitaires avec unittest ont été introduits. 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 4.2.6), add_to_end(valeur) (question 4.2.7) ou insert(valeur) (question 4.2.10). 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 11 : Listes chainées

Mission 11 : Listes chainées

1   Description

Lors de cette dernière mission, vous allez revenir une dernière fois vers votre logiciel de lecture multimédia. Une des fonctionnalités les plus demandées est la capacité de trier ses listes de lecture selon différents critères, comme la durée, l'ordre alphabétique de l'auteur, du média ou de l'album, etc. Dans cette mission vous allez implémenter cette fonctionnalité en utilisant une liste chaînée ordonnée (ordered linked list).

Vous trouverez sur INGInious une grande partie du code nécessaire, sauf la classe OrderedLinkedList que vous devez produire, ainsi qu'une nouvelle implémentation de ListeLecture nommée ListeLectureOrdonnee. Vous devez également écrire des fichiers de test détaillées (TestOrderedLinkedList.py et TestListeLectureOrdonnee.py) en utilisant des assert pour vérifier votre implémentation de ces classes. Des fichiers de test TestMedia.py et TestListeLecture.py pour tester, respectivement, l'implémentation des classes Media et ListeLecture, vous sont fournis comme exemple.

En plus de ces fichiers de tests et le code des classes Duree, Media et ListeLecture, vous aurez également accès à une implémentation complète de la classe LinkedList et son fichier de test TestLinkedList.py. Vous pouvez vous baser sur cette implémentation de la classe LinkedList pour implémenter la classe OrderedLinkedList.

2   Étapes

Voici les étapes à suivre.

2.1   Fichiers

Pour commencer, vous avez à votre disposition les fichiers suivants:

  • ListeLecture.py: contient la classe ListeLecture, ou les médias sont stockées dans une simple liste Python.
  • Duree.py et Media.py: contiennent les classes Duree et Media nécessaires pour le bon fonctionnement de la classe ListeLecture.
  • LinkedList.py: contient une implémentation complète de la classe LinkedList, qui vous servira de base ou comme inspiration pour l'OrderedLinkedList.
  • OrderedLinkedList.py: contient une classe OrderedLinkedList vide que vous devrez entièrement implémenter.
  • TestMedia.py, TestListeLecture.py et TestLinkedList: contiennent un ensemble de tests des classes Media, ListeLecture et LinkedList et servent comme exemple pour vos propres fichiers tests à créer pour les classes que vous implémenterez.

Lisez bien le code fourni dans ces fichiers pour bien comprendre sa structure et son comportement.

2.2   La classe ListeLecture

Dans un premier temps, adaptez la classe ListeLecture afin qu'elle utilise une liste chainée LinkedList comme structure de données plutôt qu'une simple liste Python. Cette étape vous facilitera le travail pour après implémenter une classe ListeLectureOrdonnee qui utilse une liste chainée ordonnée OrderedLinkedList comme structure de données.

2.3   La classe OrderedLinkedList

Une liste chaînée ordonnée fonctionne exactement comme une liste chaînée ordinaire, sauf qu'au lieu d'insérer les éléments au début, il faut les insérer de sorte qu'un certain ordre soit respecté.

Pour l'implémentation de votre OrderedLinkedList, basez-vous sur la classe LinkedList qui vous est fournie, et étendez-la si nécessaire de sorte qu'elle puisse répondre à tous vos besoins dans l'implémentation de ListeLectureOrdonnee. Il faudra donc au minimum les méthodes suivantes :

class OrderedLinkedList:

    def __init__(self, lst=[]):
        """
        Initialises a new linked list object, with a given list of elements lst.
        @pre:  -
        @post: A new ordered linked list object has been initialised.
               Its length is equal to the number of elements in lst.
               The data elements stored in the linked list's nodes correspond to those of the list lst,
               and appear in the same order as in that list.
               If no list lst is passed as argument, the newly created linked list
               will have 0 length, contain no nodes and its head will point to None.
        """
        pass

    def size(self):
        """
        Accessor method which returns the number of nodes contained in this ordered linked list.
        @pre:  -
        @post: Returns the number of nodes (possibly zero) contained in this ordered linked list.
        """
        pass

    def add(self, cargo):
        """
        Adds a new Node with given cargo to the ordered linked list such that the order is maintained
        @pre:  self is a (possibly empty) LinkedList.
        @post: A new Node object is created with the given cargo.
               This new Node is added to the ordered linked list in an ordered manner.
               The length counter has been incremented by 1.
        """
        pass

    def remove(self, cargo):
        """
        Removes the first node with a given cargo from the list.
        Leaves the list intact if already empty or if a node with that cargo
        doesn't exist.
        """
        pass

Ensuite, créez un fichier de tests TestOrderedLinkedList.py afin de tester le bon fonctionnement de votre implémentation.

2.4   La classe Media

Cette classe est déjà presque complète, mais afin de pouvoir mettre des médias "dans l'ordre", il faut définir les critères selon lequel on établit cet ordre.

Vous devez choisir un critère au choix (durée, taille, titre, artiste, etc.) et ensuite implémenter la méthode magique __lt__ (pour 'less than') ou __gt__ (pour 'greater than') dans la classe Media afin de pouvoir comparer différentes instances de celle-ci à l'aide de l'opérateur usuel < (ou >). Tout comme la méthode __eq__ permet d'utiliser l'opérateur == sur des objets des classes créée par vous, les méthodes magiques __lt__ et __gt__ vous permettent d'utiliser < et > respectivement. L'implémentation se fait suivant le pseudo code suivant :

def __lt__(self, other) :
    if not isinstance(other, Media) : return False
    return [self plus petit que other]

Bien évidemment, ce que signifie "self plus petit (/plus grand) que other" est à vous de déterminer.

Il peut être utile de compléter le tests dans le fichier TestMedia.py afin de vérifier que l'implémentation de l'ordre est correcte.

2.5   La classe ListeLectureOrdonnee

Cette classe doit implémenter les mêmes méthodes que ListeLecture, mais en utilisant une liste chaînée ordonnée à la place d'une liste python classique. À savoir:

class ListeLectureOrdonnee:
    """
    @pre:  name est un string
           id est un entier
    @post: une instance de 'ListeLecture' ayant un identifiant unique.
    """
    def __init__(self, name, id) :
        pass

    """
    Ajoute 'media' à la liste de lecture
    @pre:  media est une instance de 'Media'
    @post: la liste de lecture comprend maintenant 'media'
    """
    def ajouter(self, media) :
        pass

    """
    Retire 'media' la première occurence de 'media' à la liste de lecture
    @pre:  media est une instance de 'Media'
    @post: la première instance de 'media' dans la liste de lecture
           a été supprimé
    """
    def retirer(self, media) :
        pass

    """Renvoie le nombre de medias dans liste"""
    def nombre_de_medias(self) :
        pass

    """
    Renvoie un string avec une description de la liste
    et tous ses éléments.
    """
    def __str__(self) :
        pass

Ensuite, créez un fichier de tests TestListeLectureOrdonnee.py comme précédémment.

3   BONUS

Vous remarquerez sans doute que dans le code final, certaines classes ont des méthodes très similaires à d'autres. (Par exemple les classes OrderedLinkedList et LinkedList, ou les classes ListeLectureOrdonnee et ListeLecture.) Pour entrainer votre intuition pour l'examen, nous vous proposons d'essayer de faire hériter certaines classes d'autres classes existantes. Votre implémentation sera bien évidemment corrigée par votre tuteur·trice. En particulier réfléchissez si les classes ListeLectureOrdonnee ou OrderedLinkedList peuvent être implémentées en utilisant l'héritage.

4   Remise de votre solution


        
        
Questions complémentaires

Questions complémentaires

1   Méthode __str__ de la classe LinkedList


        
        

Ajoutez également une méthode test correspondante à votre classe test pour vérifier le bon comportement de la méthode __str__.

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

2   Méthode remove_from_end de la classe LinkedList


        
        

Ajoutez également une méthode test correspondante à votre classe test pour vérifier le bon comportement de la méthode remove_from_end.

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

3   Méthode contains de la classe LinkedList

Ajoutez une méthode contains(e) dans la classe LinkedList qui retourne True si la valeur e se trouve dans la liste et False autrement.

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Ajoutez également une méthode test correspondante à votre classe test pour vérifier le bon comportement de la méthode contains(e). N'oubliez pas de prendre en compte différents cas comme:

* la liste est vide;
* la liste contient un seul élément égal à l'élément cherché;
* la liste contient un seul élément différente de l'élément cherché;
* la liste contient différents fois un même élément;
* la liste contient différents fois l'élément cherché.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

4   Lost Children


        
        

5   Classe LinkedList


        
        

6   Turing Machine


        
        

7   Liste doublement chaînée

Adaptez le code de la classe LinkedList pour créer une classe DoubleLinkedList représentant une liste doublement chaînée. Pour chaque noeud, en plus d'avoir un lien vers le noeud suivant, on ajoute un lien vers le noeud précédent. Ceci permet de parcourir la liste dans les deux sens: avancer en suivant la référence vers le noeud suivant, ou reculer en suivant la référence vers le noeud précédent. Votre classe DoubleLinkedList doit au moins fournir les méthodes suivantes:

  • add(e) pour ajouter un élément e en tête de la liste;
  • add_to_end(e) pour ajouter un élément e en fin de la liste;
  • remove_at_start() pour supprimer l'élément en tête de la liste;
  • contains(e) pour vérifier si l'élément e se trouve dans la liste;
  • size() retourne la taille de la liste;
  • isEmpty() vérifie si la liste est actuellement vide.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Ecrivez également une classe test unitaire TestDoubleLinkedList pour vérifier le bon comportement des différentes méthodes de la classe DoubleLinkedList. Cette classe test doit contenir des tests pour les différentes méthodes de la classe DoubleLinkedList.

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

8   Méthode remove de la classe DoubleLinkedList

Au code de la liste doublement chaînée de la question complémentaire précédente, ajoutez une méthode remove(e) pour supprimer la première occurrence de e de la liste doublement chainée.

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Ajoutez également une méthode test correspondante à votre classe test pour vérifier le bon comportement de la méthode remove.