1.4. Composition d’objets

Nous avons jusqu’à présent considéré des objets relativement simples dont les attributs étaient des types de base de Python. Il est l’heure de reprendre notre classe University et de changer ses attributs nb_students et nb_professors pour qu’ils contiennent la liste des étudiants et des professeurs à la place de leur nombre. Il ne s’agira donc pas de listes d’entiers ni de chaînes de caractères, mais bien d’objets de type Student et Professor, c’est-à-dire de nos propres classes.

Nous choisissons d’initialiser ces deux listes à l’état vide et d’ajouter les méthodes University.add_student() et University.add_professor() pour les peupler. Afin de nous assurer que seuls des objets de type Student et Professor sont ajoutés via la méthode correspondante, nous utilisons la fonction isinstance(instance, Class) qui indique si un objet est une instance d’une classe donnée en paramètre ou d’une de ses sous-classes.

Note

Il est important de noter que les classes filles sont considérées comme des instances de toutes leurs classes mères ! Ainsi, dans notre exemple, les instances de Bachelor et de Master sont des instances de Student, de Academic et de Human, mais pas de Professor.

Les classes Human, Academic, Professor, Student, Bachelor et Master ne sont pas modifiées par rapport à la version précédente. Nous les incluons néanmoins dans le code ci-dessous pour que le programme soit complet et directement exécutable.

class Human:

    def __init__(self, first, last):
        self.first = first
        self.last = last

    def greet(self):
        print(f"Bonjour, je suis {self.first} {self.last}.")


class Academic(Human):

    def __init__(self, first, last, faculty):
        Human.__init__(self, first, last)
        self.faculty = faculty

    def greet(self):
        print(f"Bonjour, je suis {self.first} {self.last}.\n"
              f"Je suis universitaire en {self.faculty}.")


class Professor(Academic):

    def __init__(self, first, last, faculty, hours):
        Academic.__init__(self, first, last, faculty)
        self.hours = hours

    def greet(self):
        print(f"Bonjour, je suis {self.first} {self.last}.\n"
              f"Je suis enseignant en {self.faculty}.\n"
              f"Je donne {self.hours} heures de cours par semaine.")


class Student(Academic):

    def __init__(self, first, last, faculty, year, average):
        Academic.__init__(self, first, last, faculty)
        self.year = year
        self.average = average

    def greet(self):
        print(f"Bonjour, je suis {self.first} {self.last}.\n"
              f"Je suis étudiant de bachelor en {self.faculty}.\n"
              f"Ma moyenne est de {self.average}.\n"
              f"Je suis actuellement en {self.year}e année.")


class Bachelor(Student):

    def __init__(self, first, last, faculty, year, average):
        Student.__init__(self, first, last, faculty, year, average)

    def greet(self):
        print(f"Bonjour, je suis {self.first} {self.last}.\n"
              f"Je suis étudiant de bachelor en {self.faculty}.\n"
              f"Ma moyenne est de {self.average}.\n"
              f"Je suis actuellement en {self.year}e année.\n"
              f"J'aurai terminé dans {3 - self.year} an(s) !")


class Master(Student):

    def __init__(self, first, last, faculty, year, average):
        Student.__init__(self, first, last, faculty, year, average)

    def greet(self):
        print(f"Bonjour, je suis {self.first} {self.last}.\n"
              f"Je suis étudiant de master en {self.faculty}.\n"
              f"Ma moyenne est de {self.average}.\n"
              f"Je suis actuellement en {self.year}e année.\n"
              f"J'aurai terminé dans {2 - self.year} an(s) !")


class University:

    students = []
    professors = []

    def __init__(self, name, faculties):
        self.name = name
        self.faculties = faculties

    def add_professor(self, professor):
        if isinstance(professor, Professor):   # on vérifie le type
            self.professors.append(professor)  # on l'ajoute si compatible
        else:                                  # sinon on indique l'erreur
            print(f"L'objet n'est pas un professeur (type {type(professor)}).")

    def add_student(self, student):
        if isinstance(student, Student):
            self.students.append(student)
        else:
            print(f"L'objet n'est pas un étudiant (type {type(student)}).")

    def present(self, long=True):
        # cette fois, on affiche la longueur des listes
        print(f"L'université de {self.name} compte {len(self.students)} étudiants "
              f"et {len(self.professors)} professeurs dans {len(self.faculties)} facultés :")
        if long:
            for fac in self.faculties:
                print(f"- {fac}")
        else:
            for i in range(len(self.faculties)):
                print(f"- {self.faculties[i]}")
                if i == 1 and len(self.faculties) > 3:
                    print("- ...")
                    break

    def present_members(self, long=True):
        print("Professeurs :")
        for professor in self.professors:  # pour chaque professeur...
            if long:
                professor.greet()          # on lui demande de se présenter
            else:                          # ou on affiche son prénom et nom
                print(f"- {professor.first} {professor.last}")
        print("Étudiants :")
        for student in self.students:
            if long:
                student.greet()
            else:
                print(f"- {student.first} {student.last}")


# on fournit les paramètres souhaités pour l'initialisation de l'instance
u_micro = University(
    "Micropolis", ["Droit", "Économie", "Lettres", "Sciences"])

# on essaie d'ajouter un entier à la liste des étudiants (erreur)
u_micro.add_student(42)
# on essaie d'ajouter un étudiant à la liste des professeurs (erreur)
u_micro.add_professor(Student("Camille", "Dubois", "histoire", 1, 3.8))
# on ajoute des étudiants de bachelor
u_micro.add_student(Bachelor("Benjamin", "Legrand", "droit", 3, 5.1))
u_micro.add_student(Bachelor("Emma", "Dupont", "biologie", 2, 5.3))
# puis on ajoute des étudiants de master
u_micro.add_student(Master("Alice", "Martin", "chimie", 2, 4.3))
u_micro.add_student(Master("David", "Petit", "informatique", 1, 4.8))
# enfin on ajoute des professeurs
u_micro.add_professor(Professor("Brian", "Smith", "informatique", 7))
u_micro.add_professor(Professor("Clara", "Jones", "histoire", 6))

u_micro.present()
u_micro.present_members(False)
L'objet n'est pas un étudiant (type <class 'int'>).
L'objet n'est pas un professeur (type <class '__main__.Student'>).
L'université de Micropolis compte 4 étudiants et 2 professeurs dans 4 facultés :
- Droit
- Économie
- Lettres
- Sciences
Professeurs :
- Brian Smith
- Clara Jones
Étudiants :
- Benjamin Legrand
- Emma Dupont
- Alice Martin
- David Petit

On note que le programme affiche bien un message d’erreur lorsque l’on essaie d’ajouter un objet de mauvais type dans une liste d’étudiants ou de professeurs.

Le code de notre programme peut sembler long pour les tâches qu’il accomplit, mais il est à présent bien structuré et pourra aisément être modifié ou amélioré par la suite. Il sera également beaucoup plus facile à déboguer.