Les classes font partie d'un paradigme (méthode) de programmation appelé programmation orientée objet (POO). La programmation orientée objet essaie de simuler la réalité à l'aide d'objets. Elle se concentre sur la création de types d'objets réutilisables appelés classes.
Lorsque l'on souhaite utiliser une classe dans un programme, on crée alors un objet à partir de cette classe, d'où l'expression « orienté objet ».
nbre = 1.7
type(nbre)
verite = True
type(verite)
# Une cgaîne de caractères
ma_chaine = "abracadabra"
type(ma_chaine)
ma_chaine.upper()
# Une nouvelle liste
ma_liste = [1, 3]
type(ma_liste)
ma_liste.append(5)
ma_liste
ma_liste.insert(1, "a")
ma_liste
# Un nouveau tuple
mon_tuple = ("a", "b", "c", "a")
type(mon_tuple)
mon_tuple.count("a")
# Un nouveau dictionnaire
mon_dico = {'one': 1, 'two': 2, 'three': 3}
type(mon_dico)
cles = list(mon_dico.keys())
print(cles)
etc.
Les objets (mon_string, ma_liste, mon_tuple, mon_dico
) sont des exemples de chacune de leur classe.
Ces objets créés héritent des propriétés (attributs) et des comportements (méthodes) propres à la classe à laquelle ils sont issues.
Par exemple, on peut mesurer la longueur d'une séquence (str, tuple, list) à l'aide de la fonction len()
, insérer un objet dans une liste à l'aide la méthode append()
, récupérer les clés d'un dictionnaire grâce à la méthode keys()
...
Un type défini par l'utilisateur est également appelé une classe.
class Point:
"""
Représente un point dans le plan
"""
pass
L'objet classe est comme une usine de création d'objets. Pour créer un point, on appelle Point comme s'il s'agissait d'une fonction.
#Créer le point A
A = Point()
print(A)
La procédure print()
n'est pas encore définie pour notre nouvelle classe Point !
La valeur de retour est une référence à un objet Point (dont l'adresse mémoire est 0x000001ABEA09E6A0), que nous identifions grâce à la variable A.
La création d'un nouvel objet s'appelle instanciation, et l'objet créé est une instance de la classe.
B = Point()
print(B)
Python est un langage de programmation orienté objet, ce qui signifie qu'il fournit des fonctionnalités qui prennent en charge la programmation orientée objet.
Créons une classe Personne
class Personne:
"""
Représente une personne qui fréquente le lycée.
"""
# C'est l'usine à fabriquer des personnes qui fréquentent le lycée
pass
#Un autre exemple
class Fusee:
"""
Modélise une fusée dans un jeu
ou une simulation en physique.
"""
pass
__init__
Les propriétés que tous les objets (personnes) issus de la classe (l'usine Personne) doivent avoir sont définies dans une méthode spéciale (méthode dunder) appelée __init__
(deux caractères de soulignement, suivis de init, puis de deux autres traits de soulignement).
Chaque fois qu'un nouvel objet personne est instancié (est créé), __init__()
définit l'état initial de l'objet en attribuant les valeurs des propriétés de l'objet. Autrement dit, il initialise chaque nouvelle instance de la classe.
class Personne:
"""
Représente une personne qui fréquente le lycée ;
chacune ayant un prénom, un nom et un âge.
"""
ecole = "Saint Pierre Institut" #Attribut (variable) de classe
def __init__(self, prenom, nom, age):
print("Une nouvelle personne vient d'intégrer le lycée !")
self.prenom = prenom
self.nom = nom
self.age = age
Les attributs créés dans __init__() sont appelés attributs d'instance (des données locales). La valeur d'un attribut d'instance est spécifique à une instance particulière (personne) de la classe Personne : tous les objets (personnes) ont un prénom, un nom et un âge mais, les valeurs des attributs prenom
, nom
et age
sont propres à chaque personne.
Les attributs de classe sont des attributs qui ont la même valeur pour toutes les instances de classe. On peut définir un attribut de classe en affectant une valeur à un nom de variable en dehors de la méthode __init__().
Par exemple, toutes les personnes (toutes les instances) créées sont de Saint Pierre Institut (attribut de classe).
#Créer une instance (un élève)
personne1 = Personne("Moïse", "Dupond", 16)
#L'objet personne1 est-il une instance de Personne ?
isinstance(personne1, Personne)
personne2 = Personne("Ben", "Achraf", 15)
#L'objet personne2 est-il une instance de la classe Personne ?
isinstance(personne2, Personne)
#Les instances personne1 et personne2 sont-elles les mêmes ?
personne1 == personne2
#attribut d'instance
personne1.nom
#attribut d'instance
personne2.age
#attribut de classe
personne1.ecole
#attribut de classe
personne2.ecole
#Le même attribut de classe
Personne.ecole
#Un autre exemple
class Fusee:
"""
Modélise une fusée dans un jeu
ou une simulation en physique.
Chaque fusée est initialement à la position x= 0 et y=0
"""
def __init__ (self, x=0, y=0):
print("Une fusée vient d'être créée !")
self.x = x
self.y = y
# Créez une instance de Fusée
ma_fusee = Fusee(1, 25)
print(ma_fusee)
L'un des principaux avantages de l'utilisation de classes pour organiser les données est que les instances sont garanties d'avoir les attributs que l'on attend. Ainsi, toutes les instances ont des attributs prénom, nom et age.
Toutefois, bien que l'existence des attributs soit garantie, leurs valeurs peuvent être modifiées dynamiquement par l'utilisateur final !
#On modifie l'âge de personne1
personne1.age = 18
personne1.age
#On modifie l'attribut de classe
personne1.ecole = "Lycée Descartes"
del personne1.nom
personne1.nom
#espace de noms
vars(Personne)
#espace de noms
vars(personne1)
personne1.__dict__
#espace de noms
vars(personne2)
personne2.__dict__
Les méthodes d'instance sont des fonctions définies à l'intérieur d'une classe et ne peuvent être appelées qu'à partir d'une instance de cette classe. Tout comme __init__(), le premier paramètre d'une méthode d'instance est toujours self. L'appel d'une méthode se fait à l'aide de la notation pointée.
class Personne:
"""
Représente une personne qui fréquente le lycée ;
chacune ayant un prénom, un nom et un âge.
"""
ecole = "Lycée Saint Pierre" #variable de classe
def __init__(self, prenom, nom, age):
assert isinstance(age, int)
assert isinstance(prenom, str)
assert isinstance(nom, str)
self.prenom = prenom
self.nom = nom
self.age = age
# Une méthode d'instance
def presentation(self):
return f"{self.prenom} {self.nom} agé(e) de {self.age} ans."
Margot = Personne("Margot", "Yacine", 15)
print(Margot)
Margot.presentation()
#Un autre exemple
class Fusee:
"""
Modélise une fusée dans un jeu
"""
def __init__ (self, x = 0, y = 0):
assert isinstance(x, (int, float)) and isinstance(y, int|float)
#x et y sont soit des int soit des float
self.x = x
self.y = y
def mettre_en_mouvement(self, pas_horizontal = 0, pas_vertical = 1):
# Incrémentez la position y de la fusée.
self.x += pas_horizontal
self.y += pas_vertical
# Créer un objet Fusee et commencer à le faire monter.
ma_fusee = Fusee()
print(f"Altitude de la fusée : {ma_fusee.y}")
ma_fusee.mettre_en_mouvement()
print(f"Altitude de la fusée : {ma_fusee.y}")
ma_fusee.mettre_en_mouvement()
print(f"Altitude de la fusée : {ma_fusee.y}")
Exercice :
__str__ ( __repr__ ) est une méthode spéciale, comme __init__, qui est censée renvoyer une représentation sous forme de chaîne de caractères d'un objet.
Autrement dit, lorsque l'on utilise l'instruction print()
sur un objet, il invoque la méthode str
(repr
).
repr("a")
repr(1)
str("a")
str(1)
class Personne:
"""
Représente une personne qui fréquente le lycée ;
chacune ayant un prénom, un nom et un âge.
"""
ecole = "Lycée Saint Pierre" #variable de classe
def __init__(self, prenom, nom, age):
self.prenom = prenom
self.nom = nom
self.age = age
# méthode d'instance
def __str__(self):
return f"{self.prenom} {self.nom} agé(e) de {self.age} ans."
Margot = Personne("Margot", "Yacine", 15)
print(Margot)
Margot
class Personne:
"""
Représente une personne qui fréquente le lycée ;
chacune ayant un prénom, un nom et un âge.
"""
ecole = "Lycée Saint Pierre" #attribut (variable) de classe
def __init__(self, prenom, nom, age):
self.prenom = prenom
self.nom = nom
self.age = age
# méthode d'instance
def __str__(self):
return f"{self.prenom} {self.nom} agé(e) de {self.age} ans."
# méthode d'instance
def __repr__(self) -> str:
return f"{type(self).__name__}(prénom = {self.prenom}, nom = {self.nom}, âge = {self.age})"
Margot = Personne("Margot", "Aïssatou", 15)
Margot
class Personne:
"""
Représente une personne qui fréquente le lycée ;
chacune ayant un prénom, un nom et un âge.
"""
ecole = "Lycée Saint Pierre" #variable de classe
def __init__(self, prenom, nom, age):
self.prenom = prenom
self.nom = nom
self.age = age
self.nom_prenom = self.nom + self.prenom
# méthode d'instance
def __str__(self):
return f"{self.prenom} {self.nom} agé(e) de {self.age} ans."
# Une méthode d'instance
def __repr__(self) -> str:
return f"{type(self).__name__}(prénom = {self.prenom}, nom = {self.nom}, âge = {self.age})"
#Une autre méthode d'instance
def __len__(self):
"""
Renvoie le nombre de lettres contenues dans nom_prenom.
"""
return len(self.nom_prenom)
#Encore une autre méthode
def __contains__(self, lettre):
"""
Renvoie True si lettre est dans self.nom_prenom, False dans le cas contraire.
"""
return lettre in self.nom_prenom
Margot = Personne("Margot", "Yacine", 15)
len(Margot)
"e" in Margot
#Un autre exemple
class Fusee:
"""
Modélise une fusée dans un jeu
"""
def __init__ (self, x = 0, y = 0):
# Chaque fusée a une position (x ; y)
assert isinstance(x, (int, float)) and isinstance(y, (int, float))#x et y sont soit des int soit des float
self.x = x
self.y = y
def mettre_en_mouvement(self, pas_horizontal = 0, pas_vertical = 1):
# Incrémentez la position y de la fusée.
self.x += pas_horizontal
self.y += pas_vertical
# Créer un objet Fusee et commencer à le faire monter, comme suit.
ma_fusee = Fusee()
print(f"Altitude de la fusée : {ma_fusee._y}")
ma_fusee.mettre_en_mouvement()
print(f"Altitude de la fusée : {ma_fusee._y}")
ma_fusee.mettre_en_mouvement()
print(f"Altitude de la fusée : {ma_fusee._y}")
fusee2
, qui a pour abscisse x = 3
et y = 10
.ma_fusee
et fusee2
L'un des rôles principaux d'une classe est d'encapsuler (de cacher, d'envelopper) les données et les détails d'implémentation internes d'un objet. L'objectif étant de protéger les objets de la classe des usagers qui seraient tentés de modifier directectement leur état.
La conséquence : les objets ne sont accessibles qu'au travers de leur interface c'est-à-dire via leurs attributs et méthodes.
En Python, par défaut, les attributs sont publics, donc modifiables par l'utilisateur final. Mais quelquefois, on ne souhaite pas que l'usager altère certains attributs d'une instance :
class Personne:
"""
Représente une personne qui fréquente le lycée ;
chacune ayant un prénom, un nom et un âge.
"""
ecole = "Saint Pierre Institut" #Attribut (variable) de classe
def __init__(self, prenom, nom, age):
print("Une nouvelle personne vient d'intégrer le lycée !")
self._prenom = prenom
self._nom = nom
self._age = age
#Créer une instance (un élève)
personne1 = Personne("Dupond", 16)
personne1._nom
le tiret bas ( _ ) qui préfixe une variable permet simplement de signaler aux autres programmeurs que ladite variable est à usage interne ; mais elle demeure accessible et modifiable.
personne1._nom
class Personne:
"""
Représente une personne qui fréquente le lycée ;
chacune ayant un prénom, un nom et un âge.
"""
ecole = "Saint Pierre Institut" #Attribut (variable) de classe
def __init__(self, prenom, nom, age):
print("Une nouvelle personne vient d'intégrer le lycée !")
self__prenom = prenom
self.__nom = nom
self.__age = age
#Créer une instance (un élève)
personne1 = Personne("Dupond", "Anaïs", 16)
personne1.__nom
dir(personne1)
Malgré le double tiret bas ( _ ) qui préfixe une variable, elle reste toujours accessible de l'extérieur. Mais le code est un peu plus complexe que précedemment.
personne1._Personne__nom
En Python, il est conseillé d'être aussi explicite que possible. L'usage d'un décorateur (@property
) permet de mieux contrôler l'accès aux attributs et méthodes.
#Ici, tous les attributs sont accessibles en lecture, écriture et suppression
class Personne:
"""
Représente une personne qui fréquente le lycée ;
chacune ayant un prénom, un nom et un âge.
"""
ecole = "Saint Pierre Institut" #Attribut (variable) de classe
def __init__(self, prenom, nom, age):
print("Une nouvelle personne vient d'intégrer le lycée !")
self._prenom = prenom
self._nom = nom
self._age = age
@property
def prenom(self):
"""Récupère le prénom de l'instance (personne.)"""
return self._prenom
@prenom.setter
def prenom(self, nouveau_nom):
"""Modifie le prénom de l'instance."""
self._prenom = nouveau_nom
@prenom.deleter
def prenom(self):
"""Supprime le prénom de l'instance."""
del self._prenom
@property
def nom(self):
"""Récupère le nom de l'instance (personne.)"""
return self._nom
@nom.setter
def nom(self, nouveau_nom):
"""Modifie le nom de l'instance."""
self._nom = nouveau_nom
@nom.deleter
def nom(self):
"""Supprime le nom de l'instance."""
del self._nom
@property
def age(self):
"""Obtient l'âge de l'instance."""
return self._age
@age.setter
def age(self, nouvel_age):
"""Change l'âge de l'intance."""
self._age = nouvel_age
@age.deleter
def age(self):
"""Supprime l'âge de l'instance"""
del self._age
# méthode d'instance
def __str__(self):
return f"{self.prenom} {self.nom} agé(e) de {self.age} ans."
# Une méthode d'instance
def __repr__(self) -> str:
return f"{type(self).__name__}(prénom = {self.prenom}, nom = {self.nom}, âge = {self.age})"
#Créer une instance (un élève)
personne1 = Personne("Mathieu", "Dupond", 16)
personne1.nom
personne1.age
#espace de nom
vars(personne1)
#Ici, les attributs ne sont accessibles qu'en lecture
#C'est simplement une information pour les programmeurs !
class Personne:
"""
Représente une personne qui fréquente le lycée ;
chacune ayant un prénom, un nom et un âge.
"""
ecole = "Saint Pierre Institut" #Attribut (variable) de classe
def __init__(self, prenom, nom, age):
print("Une nouvelle personne vient d'intégrer le lycée !")
self._nom = nom
self._prenom = prenom
self._age = age
@property
def prenom(self):
"""Récupère le prénom de l'instance (personne.)"""
return self._prenom
@property
def nom(self):
"""Obtient le nom de l'instance (personne.)"""
return self._nom
@property
def age(self):
"""Obtient l'âge de l'instance."""
return self._age
# méthode d'instance
def __str__(self):
return f"{self.prenom} {self.nom} agé(e) de {self.age} ans."
# Une méthode d'instance
def __repr__(self) -> str:
return f"{type(self).__name__}(prénom = {self.prenom}, nom = {self.nom}, âge = {self.age})"
#Créer une instance (un élève)
personne1 = Personne("Mathieu", "Dupond", 16)
personne1._nom
personne1._nom = 'rose'
personne1._nom
Une classe permet de créer des objets (instances). La classe et ses objets sont liés par une relation d'héritage. Autrement dit, les objets reçoivent tous les attributs et méthodes définis dans la classe.
Par ailleurs, l'héritage permet de créer un sous-ensemble d'une classe. Ainsi, il permet d'étendre une classe, notamment par
#Ici, tous les attributs sont accessibles en lecture, écriture et suppression
class Personne:
"""
Représente une personne qui fréquente le lycée ;
chacune ayant un prénom, un nom et un âge.
"""
ecole = "Saint Pierre Institut" #Attribut (variable) de classe
def __init__(self, prenom, nom, age):
print("Une nouvelle personne vient d'intégrer le lycée !")
self._prenom = prenom
self._nom = nom
self._age = age
@property
def prenom(self):
"""Récupère le prénom de l'instance (personne.)"""
return self._prenom
@prenom.setter
def prenom(self, nouveau_nom):
"""Modifie le prénom de l'instance."""
self._prenom = nouveau_nom
@prenom.deleter
def prenom(self):
"""Supprime le prénom de l'instance."""
del self._prenom
@property
def nom(self):
"""Récupère le nom de l'instance (personne.)"""
return self._nom
@nom.setter
def nom(self, nouveau_nom):
"""Modifie le nom de l'instance."""
self._nom = nouveau_nom
@nom.deleter
def nom(self):
"""Supprime le nom de l'instance."""
del self._nom
@property
def age(self):
"""Obtient l'âge de l'instance."""
return self._age
@age.setter
def age(self, nouvel_age):
"""Change l'âge de l'intance."""
self._age = nouvel_age
@age.deleter
def age(self):
"""Supprime l'âge de l'instance"""
del self._age
# méthode d'instance
def __str__(self):
return f"{self.prenom} {self.nom} agé(e) de {self.age} ans."
# Une méthode d'instance
def __repr__(self) -> str:
return f"{type(self).__name__}(prénom = {self.prenom}, nom = {self.nom}, âge = {self.age})"
class Eleve(Personne):
def __init__(self, prenom, nom, age, niveau):
super().__init__(prenom, nom, age)#On charrie l'initialisation de la classe parent
self.niveau = niveau
eleve1 = Eleve("Claude", "Adam", 16, "première")
isinstance(eleve1, Eleve)
isinstance(eleve1, Personne)
eleve1._prenom
eleve1.ecole
print(eleve1)
#Un autre exemple
class Fusee:
"""
Modélise une fusée dans un jeu
"""
def __init__ (self, x = 0, y = 0):
# Chaque fusée a une position (x ; y)
assert isinstance(x, (int, float)) and isinstance(y, (int, float))#x et y sont soit des int soit des float
self._x = x
self._y = y
#Attributs en lecture seule
@property
def x(self):
"""Récupère la position x de la fusée."""
return self._x
@property
def y(self):
"""Récupère la position y de la fusée."""
return self._y
def mettre_en_mouvement(self, pas_horizontal = 0, pas_vertical = 1):
# Incrémentez la position y de la fusée.
self._x += pas_horizontal
self._y += pas_vertical
def get_distance(self, une_autre_fusee):
import math
"""Renvoie la distance de cette fusée à une_autre_fusee."""
def __str__(self):
return f"Je me trouve à l'altitude {self._y} et horizontalement à {self._x} de mon point de départ."
# Une méthode d'instance
def __repr__(self) -> str:
return f"{type(self).__name__}(x = {self.x}, y = {self.y})"
#un autre exemple
class Navette(Fusee):
"""Modélise une navette spatiale (fusée réutilisable)."""
def __init__(self, x=0, y=0, nbre_vols=0):
super().__init__(x, y)
self.nbre_vols = nbre_vols
def __str__(self):
return f"Je suis une navette spatiale et me trouve à l'altitude {self._y} et horizontalement à {self._x} de mon point de départ."
# Une méthode d'instance
def __repr__(self) -> str:
return f"{type(self).__name__}(x = {self.x}, y = {self.y})"
navette = Navette(10, 0, 3)
print(navette)
isinstance(navette, Navette)
isinstance(navette, Fusee)
Le polymorphisme est la propriété de pouvoir traiter différents types d'objets de la même manière et de faire en sorte que chacun de ces objets réagisse à sa manière.
En POO, le polymorphisme signifie qu'une même méthode appliquée à des objets de classes différentes liées par une relation d'héritage permet d'obtenir des résultats différents mais appropriés.
Les méthodes str et repr, par exemple, sont attachées aussi bien attachées aux objet Fusee qu'aux objets Navette.
Plus le fichier contenant les classes se densifient plus il devient difficile de le manipuler. Aussi est-il conseillé d'enregistrer les classes dans un fichier à part, sous forme de module puis de les importer quand on en a besoin.
On peux enregistrer une ou plusieurs classes dans un fichier.
Exemple :
Enregistrer la classe Personne dans un fichier nommé personne.py.
#Dans un nouveau fichier contenu dans le même répertoire que personne.py
from personne import Personne
une_personne = Personne("Ben", "Yacine", 25)
print(une_personne)
Exercice :
#Ici, tous les attributs sont accessibles en lecture, écriture et suppression
class Personne:
"""
Représente une personne qui fréquente le lycée ;
chacune ayant un prénom, un nom et un âge.
"""
ecole = "Saint Pierre Institut" #Attribut (variable) de classe
def __init__(self, prenom, nom, age):
print("Une nouvelle personne vient d'intégrer le lycée !")
self._prenom = prenom
self._nom = nom
self._age = age
@property
def prenom(self):
"""Récupère le prénom de l'instance (personne.)"""
return self._prenom
@prenom.setter
def prenom(self, nouveau_nom):
"""Modifie le prénom de l'instance."""
self._prenom = nouveau_nom
@prenom.deleter
def prenom(self):
"""Supprime le prénom de l'instance."""
del self._prenom
@property
def nom(self):
"""Récupère le nom de l'instance (personne.)"""
return self._nom
@nom.setter
def nom(self, nouveau_nom):
"""Modifie le nom de l'instance."""
self._nom = nouveau_nom
@nom.deleter
def nom(self):
"""Supprime le nom de l'instance."""
del self._nom
@property
def age(self):
"""Obtient l'âge de l'instance."""
return self._age
@age.setter
def age(self, nouvel_age):
"""Change l'âge de l'intance."""
self._age = nouvel_age
@age.deleter
def age(self):
"""Supprime l'âge de l'instance"""
del self._age
# méthode d'instance
def __str__(self):
return f"{self._prenom} {self._nom} agé(e) de {self._age} ans."
# Une méthode d'instance
def __repr__(self):
return f"Personne({self._prenom}, {self._nom}, {self._age})"
#Un autre exemple
class Fusee:
"""
Modélise une fusée dans un jeu
"""
def __init__ (self, x = 0, y = 0):
# Chaque fusée a une position (x ; y)
assert isinstance(x, (int, float)) and isinstance(y, (int, float))#x et y sont soit des int soit des float
self.x = x
self.y = y
def mettre_en_mouvement(self, pas_horizontal = 0, pas_vertical = 1):
# Incrémentez la position y de la fusée.
self.x += pas_horizontal
self.y += pas_vertical
def get_distance(self, une_autre_fusee):
import math
"""Renvoie la distance de cette fusée à une_autre_fusee."""
def __str__(self):
return f"Je me trouve à l'altitude {self.y} et horizontalement à {self.x} de mon point de départ."
def __repr__(self):
return f"Fusee({self.x}, {self.y})"
#un autre exemple
class Navette(Fusee):
"""Modélise une navette spatiale (fusée réutilisable)."""
def __init__(self, x=0, y=0, nbre_vols=0):
super().__init__(x, y)
self.nbre_vols = nbre_vols
def __str__(self):
return f"Je suis une navette spatiale et me trouve à l'altitude {self._y} et horizontalement à {self._x} de mon point de départ."
# Une méthode d'instance
def __repr__(self) -> str:
return f"{type(self).__name__}(x = {self.x}, y = {self.y})"
On donne également le code suivant :
from random import randint
navettes = []
for x in range(3):
x = randint(0, 100)
y = randint(1, 100)
nbre_vols = randint(0, 10)
navettes.append(Navette(x, y, nbre_vols))
ma_flotte = []
for x in range(3):
x = randint(0, 100)
y = randint(1, 100)
ma_flotte.append(Fusee(x, y))