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.
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.
A l'issue de cette mission, chacun d'entre vous :
La matière relative à cette mission est décrite dans les sections suivantes de la partie Objects du syllabus en ligne:
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:
Les questions à choix multiples de cette mission sont accessibles en ligne depuis https://inginious.info.ucl.ac.be/course/LSINF1101-PYTHON/mission_8_QCM
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**
Dans la question précédente, expliquez les notations student.name, student.average_score() et self.name et leur interprétation par Python.
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
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##
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.
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.
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.
Chargez les fichiers "music-db.txt", "mission8.py" et "test.py".
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. """
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.
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" """
Ajoutez au fichier test.py une série de tests pour tester les différentes méthodes de la classe Chanson.
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
Ajoutez au fichier test.py une série de tests pour tester la classe Album.
Finalement, complétez votre programme par une série d'instructions qui:
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
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.
Le diagramme de classes suivant résume les différentes classes et méthodes à implémenter lors de cette mission :
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.
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:
Utilisez et testez cette classe pour créer une grande famille de personnes.
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.
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.
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:
Les questions à choix multiples de cette mission sont accessibles en ligne depuis https://inginious.info.ucl.ac.be/course/LSINF1101-PYTHON/mission_9_QCM
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.
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 :
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()
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()
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 :
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 ):
Comment feriez-vous maintenant pour définir une classe Carre qui étend la classe Rectangle et permet de représenter un carré ?
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)
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
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:
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
Voici les étapes à suivre.
Chargez les fichiers mission9.py et test.py et étudiez leurs contenus. Vous disposez des classes et fonctions suivantes:
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.
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
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)
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]
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.
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. |
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?
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 :
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 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.
Vous disposez déjà des classes suivantes :
Votre fichier mission10.py doit inclure :
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.
Prenez connaissance du fichier mission10.py et examinez la méthode save() déjà implémentée dans la classe LivreAudio.
Ajoutez la méthode save() également dans la classe Video.
Analysez en détail ce que les méthodes save() des classes LivreAudio et Video ont en commun.
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.
Pour cette mission, vous devez soumettre les fichiers suivants :
Bonnes pratiques :
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.
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.
A l'issue de cette mission, chacun d'entre vous :
Comment bien préparer la mission?
Assister au cours et regarder les capsules vidéos pour comprendre les notions et concepts qui seront abordés.
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
Essayer et comprendre le code développé dans le syllabus et qui se trouve aussi dans l'annexe correspondant :
- Appendix - Source code of linked lists
- LinkedList class
- Node class
- Creating and using LinkedList objects
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.
Répondre aux questions à choix multiples et aux autres questions 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.
Les questions à choix multiples de cette mission sont accessibles en ligne depuis https://inginious.info.ucl.ac.be/course/LSINF1101-PYTHON/mission_11_QCM.
Répondez aux questions suivantes:
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?
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() [ ]
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 ]
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 :
Pour plus de détails sur les étapes, allez voir le catalogue : Ajout d'un noeud dans une liste chainée .
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.
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?
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.
Dessinez complètement une telle liste chainée contenant les trois strings "abc", "def" et "xyz".
Expliquez en français et dessinez les opérations à effectuer pour ajouter un Node contenant "aaa" dans cette liste.
Même question avec un Node contenant "ghi".
Même question avec un Node contenant "def".
Même question avec un Node contenant "zzz".
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()
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.
Voici les étapes à suivre.
Pour commencer, vous avez à votre disposition les fichiers suivants:
Lisez bien le code fourni dans ces fichiers pour bien comprendre sa structure et son comportement.
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.
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.
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.
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.
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.
Ajoutez également une méthode test correspondante à votre classe test pour vérifier le bon comportement de la méthode __str__.
Ajoutez également une méthode test correspondante à votre classe test pour vérifier le bon comportement de la méthode remove_from_end.
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é.
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:
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.
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.