Informatique et Calcul Scientifique

Cours 6

Author

Leonard Hardiman et Ghid Maatouk

Classes en Python

Une classe permet de créer sa propre structure de données personnalisée en regroupant des données (attributs) et des fonctions (méthodes) qui opèrent sur ces données.

  • Une classe définit un type d’objet
  • On crée des instances de la classe (des objets concrets)
  • Chaque instance possède ses propres attributs (variables)
  • Les méthodes sont des fonctions associées à la classe

Définir une classe

Pour définir une classe, on utilise le mot-clé class suivi du nom de la classe (par convention en majuscule).

# Définir une classe
class Etudiant:
    pass 

# Créer une instance
e1 = Etudiant()

print(Etudiant, type(Etudiant), sep='\n')
print('\n'+'*'*20+'\n')
print(e1, type(e1), sep='\n')
<class '__main__.Etudiant'>
<class 'type'>

********************

<__main__.Etudiant object at 0x7a269a8d9f90>
<class '__main__.Etudiant'>

Méthodes d’une classe

Les méthodes sont des fonctions définies à l’intérieur d’une classe. Le premier paramètre d’une méthode est toujours self, qui représente l’instance elle-même.

Remarque. Lors de l’appel d’une méthode, Python passe automatiquement l’instance comme premier argument. On n’écrit donc pas e1.se_presenter(e1) mais simplement e1.se_presenter() (comme nous l’avons vu avec les méthodes de liste, telles que append).

class Etudiant:
    def se_presenter(self):
        print('Bonjour!')

e1 = Etudiant()
e1.se_presenter()
Bonjour!
class Etudiant:
    def se_presenter(self):
        print('Bonjour, je suis', self)

e1 = Etudiant()
e1.se_presenter()
Bonjour, je suis <__main__.Etudiant object at 0x7a269a8da440>

Le constructeur __init__

Le constructeur __init__ est une méthode spéciale appelée automatiquement lors de la création d’une instance. Elle permet (entre autre) d’initialiser des attributs de l’instance.

Un attribut est une variable attachée à une instance spécifique d’une classe (par exemple, self.nom et self.age).

class Etudiant:
    def __init__(self, n, a):
        self.nom = n
        self.age = a

    def se_presenter(self):
        print('Bonjour!')


e1 = Etudiant('Poirot', 20)
print('Nom :', e1.nom)
print('Age :', e1.age)
Nom : Poirot
Age : 20
# Avec valeurs par défaut
class Etudiant:
    def __init__(self, n, a=18):
        self.nom = n
        self.age = a

e1 = Etudiant('Poirot')
e2 = Etudiant('Hastings', 22)

print(e1.nom, e1.age)
print(e2.nom, e2.age)
Poirot 18
Hastings 22

Remarque. Chaque instance possède ses propres attributs. Modifier l’attribut d’une instance n’affecte pas les autres instances.

class Etudiant:
    def __init__(self, n, a):
        self.nom = n
        self.age = a

e1 = Etudiant('Poirot', 20)
print('Age initial:', e1.age)

# Modification de l'attribut
e1.age += 1
print('Nouvel age:', e1.age)
Age initial: 20
Nouvel age: 21

Inspecter les attributs : vars

La fonction vars permet d’inspecter tous les attributs d’une instance sous forme de dictionnaire.

class Rectangle:
    def __init__(self, l, h):
        self.largeur = l
        self.hauteur = h
    
    def aire(self):
        return self.largeur * self.hauteur

r = Rectangle(5, 3)
print('vars(r):', vars(r))
vars(r): {'largeur': 5, 'hauteur': 3}

Remarque. La fonction dir renvoie tous les noms disponibles sur un objet (attributs et méthodes), y compris les méthodes spéciales entourées de __ (comme init, str) que Python invoque automatiquement.

class Rectangle:
    def __init__(self, l, h):
        self.largeur = l
        self.hauteur = h
    
    def aire(self):
        return self.largeur * self.hauteur

r = Rectangle(5, 3)
print('dir(r):', dir(r))
dir(r): ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'aire', 'hauteur', 'largeur']

Méthodes utilisant les attributs

Les méthodes peuvent accéder et modifier les attributs de l’instance via self.

class Etudiant:
    def __init__(self, n, a):
        self.nom = n
        self.age = a
    
    def avoir_anniversaire(self):
        self.age += 1

e1 = Etudiant('Poirot', 20)
print('Avant:', e1.age)
e1.avoir_anniversaire()
print('Après:', e1.age)
Avant: 20
Après: 21
class Rectangle:
    def __init__(self, largeur, hauteur):
        self.largeur = largeur
        self.hauteur = hauteur
    
    def aire(self):
        return self.largeur * self.hauteur

r = Rectangle(5, 3)
print('Aire:', r.aire())
Aire: 15

Personnaliser l’affichage : __str__

Par défaut, print(instance) affiche une représentation peu lisible. On peut personnaliser cet affichage en définissant la méthode spéciale __str__ qui doit retourner une chaîne de caractères.

class Etudiant:
    def __init__(self, n, a):
        self.nom = n
        self.age = a
    
    def __str__(self):
        return self.nom + ', ' + str(self.age) + ' ans'

e1 = Etudiant('Poirot', 20)
print(e1)
Poirot, 20 ans

Exemple final : plusieurs classes

Voici un exemple complet avec deux classes qui interagissent:

  • Livre
class Livre:
    def __init__(self, titre, auteur, pages):
        self.titre = titre 
        self.pages = pages
        self.auteur = auteur
        self.progression = 0
        
    def __str__(self):
        pct = str(int(self.progression * 100))
        return (self.titre + ' par ' + self.auteur + 
                ', ' + str(self.pages) + ' pages, lu à ' + 
                pct + '%')
    
    def lire(self, nb_pages):
        debut = self.progression * self.pages
        self.progression = min((debut + nb_pages) / self.pages, 1)
  • Biblio
class Biblio:
    def __init__(self, L, propriétaire):
        self.contenu = [livre for livre in L 
                         if type(livre) == Livre]
        self.propriétaire = propriétaire
    
    def livre_par_auteur(self, auteur):
        return [livre for livre in self.contenu 
                if livre.auteur == auteur]
    
    def grouper_par_auteur(self):
        d = {}
        for livre in self.contenu:
            if livre.auteur in d:
                d[livre.auteur].append(livre)
            else:
                d[livre.auteur] = [livre]
        return d
    
    def acheter_livre(self, livre):
        self.contenu.append(livre)
    
    def prêter_livre(self, livre):
        self.contenu.remove(livre)
        
    def __str__(self):
        return ('La bibliothèque de ' + 
                self.propriétaire + '. Elle contient ' + 
                str(len(self.contenu)) + ' livres.')
    
    def afficher_livres(self):
        for livre in self.contenu:
            print(livre)
  • Acheter plein de livres
from fonctions_ics import get_random_book
b = Biblio([], 'Leonard')
n = 100
for i in range(n):
    titre, auteur, pages = get_random_book()
    livre = Livre(titre, auteur, pages)
    b.acheter_livre(livre)
    print(i, '/ ' + str(n))

print(b)
print('\nPremiers livres:')
for livre in b.contenu[:5]:
    print(livre)

Voici la bibliothèque de Leonard. Elle contient 100 livres.

Premiers livres:
Stones from the River par Ursula Hegi, 334 pages, lu à 0%
Mad Dogs (Cherub, #8) par Robert Muchamore, 421 pages, lu à 0%
Ruin and Rising (Shadow and Bone, #3) par Leigh Bardugo, 169 pages, lu à 0%
Neverwhere par Neil Gaiman, 340 pages, lu à 0%
Their Eyes Were Watching God par Zora Neale Hurston, 320 pages, lu à 0%