<string>

Table des matières

Mission 8 - Classes et objets

Mission 8 : Classes et objets

Mission 8 : Classes et objets

Introduction

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

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

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

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

Objectifs

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

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

Préparation, étude et apprentissage

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

Questionnaire de démarrage

Questions à choix multiple

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


        
        

Questions ouvertes

Définitions

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

class Student :

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

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

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

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

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

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

Classes et objets

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

 
 
 
 
 
 
 
 

Méthode de conversion de string

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

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

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

print(student)

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

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

Tester votre code comme suite:

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

Une classe simple

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

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

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

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

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

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

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

 
 
 
 
 
 
 
 
 
 
 
 
 
 

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

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

Création de classes

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

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

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

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

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


        
        

Une méthode simple

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

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

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

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


        
        

Egalité

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

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

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

 
 
 
 

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


        
        

OrderedPair

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

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

class OrderedPair:

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

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

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

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


        
        

Mission 8

Mission 8

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

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

"TITRE AUTEUR MIN SEC"

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

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

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

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

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

Etapes

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

...

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

Remise de votre solution

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

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

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

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


        
        
Questions complémentaires

Questions complémentaires

SMS Store

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

my_inbox = SMS_store()

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

The inbox object should provide these methods:

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

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

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

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

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

my_inbox.clear()
    # Delete all messages from inbox

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


        
        

Création d'objets depuis un fichier

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

Voici le constructeur de la classe Student:

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

Le fichier suit le format suivant:

Kim Mens 12
Charles Pecheur 13
Siegfried Nijssen 18

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