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/Session9_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 et vérifiez que les conditions requises soient bien respectées.
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.