La programmation orientée objet (POO) est un concept de programmation très puissant qui permet de structurer ses programmes d'une manière nouvelle. Les classes sont les principaux outils de la POO. Ce type de programmation permet de structurer les logiciels complexes en les organisant comme des ensembles d'objets qui interagissent, entre eux et avec le monde extérieur. De plus, la POO amène de nouveaux concepts tels que le polymorphisme (Capacité à redéfinir le comportement des opérateurs) ou l'héritage (Capacité à définir une classe à partir d'une classe pré-existante et d'y ajouter de nouvelles fonctionnalités), concepts que nous ne développerons pas dans ce chapitre.
Mais qu'est-ce qu'une classe ?
↪ À Retenir :
Une classe peut être vue comme un moule permettant de créer autant d'objets souhaités sur un même modèle.
On appelle ces objets des instances (des représentants) de cette classe. Chaque instance peut posséder :
- Des attributs : Variables associées aux instances
- Des méthodes : Fonctions associées aux instances qui peuvent agir sur ces dernières ou encore les utiliser
Exemple :
On peut définir une classe
Eleve
qui contient les attributs suivants :
nom
: Nom de l'élèveprenom
: Prénom de l'élèveage
: ...classe
: ...ainsi que les méthodes suivantes :
souffle_bougies
: Méthode permettant d'incrémenter l'attributage
de 1change_classe
: Méthode permettant de changer declasse
Attributs | Valeurs |
nom | Machin |
prenom | Tom |
age | 17 |
classe | T1 |
Méthodes | |
souffle_bougies | |
change_classe |
Remarque :
En fait, en Python tout est objet. Une variable de type
int
est en fait un objet de typeint
, donc construit à partir de la classeint
. Il en va de même pour lesfloat
et lesstr
, mais également pour les objets de typelist
,tuple
,dict
, ... C'est la raison pour laquelle nous avons déjà rencontré la notion de méthode :
- Les méthodes
lower
etupper
pour les objets de typestr
- La méthode
sort
et la méthodeappend
pour les objets de typelist
Rappelons que la fonction
type
permet de déterminer la classe d'un objet :>>> print(type(3)) <class 'int'> >>> print(type([3, 4, 5])) <class 'list'>
On notera que les classes sont l’unique moyen en langage python de définir de nouveaux types, propres à celui qui programme.
En Python, le mot-clé class
permet de créer sa propre classe, suivi du nom de cette classe. Un nom de classe commence toujours par une majuscule et s'écrit de préférence en Camel Case
. Comme pour les fonctions, cette « ligne de définition » attend un bloc d'instructions indenté définissant le corps de la classe.
Comme pour les fonctions, On pourra écrire la documentation de la classe entre triple quotes.
La création d’un objet de type MaClasse
est identique à celle des types standards du langage Python : elle passe par une simple affectation. La syntaxe d'instanciation est identique à la syntaxe d’appel d’une fonction. La création d’une instance peut également faire intervenir des paramètres, comme nous le verrons ultérieurement.
Exemple :
# Création de la classe UneClasseVide class UneClasseVide: '''Une classe vide sans attribut ni méthode''' pass # Instanciation de deux objets de type UneClasseVide instance_1 = UneClasseVide() instance_2 = UneClasseVide() print(f'Type de instance_1 : {type(instance_1)}') print(isinstance(instance_2, UneClasseVide))
↪ À Retenir :
# Syntaxe pour définir une classe : class NomClasse: '''Documentation de la classe''' # Corps de la classe ... # Syntaxe pour instancier un objet de type NomClasse sans paramètre : nom_instance = NomClasse() # Syntaxe pour instancier un objet de type NomClasse sans paramètre : nom_instance = NomClasse(param_1, param_2, ...) # La fonction `isinstance` permet de savoir si un objet est une instance d'une classe spécifique : isinstance(nom_instance, NomClasse)
Rappelons que les attributs d'instances sont des variables associées à une instance.
La syntaxe pour accéder à un attribut d'instance, en lecture ou en écriture, est la suivante : nom_instance.attribut
.
Si l'attribut n'existe pas, il est créé pour l'instance considérée.
Exemple n°1:
Q1. Compléter le code ci-dessous en respectant les consignes données :
class Eleve: '''Classe représentant un élève''' pass # Instancier un élève e1 de la classe Eleve ... # Créer un attribut nom ayant pour valeur 'Machin' pour l'élève e1 ... # Créer un attribut prenom ayant pour valeur 'Tom' pour l'élève e1 ... # Afficher "L'élève e1 s'appelle Tom Machin" en utilisant ces attributs ... # Instancier un élève e2 de la classe Eleve ... # Créer un attribut age ayant pour valeur 17 pour l'élève e2 ... # Créer un attribut classe ayant pour valeur 'T1' pour l'élève e2 ... # Afficher "L'élève e2 est en T1 et a 17 ans" en utilisant ces attributs ...
Q2. Visualiser le code complété sur pythontutor.com.
class Eleve:
'''Classe représentant un élève'''
pass
# Instancier un élève e1 de la classe Eleve
e1 = Eleve()
# Créer un attribut nom ayant pour valeur 'Machin' pour l'élève e1
e1.nom = 'Machin'
# Créer un attribut prenom ayant pour valeur 'Tom' pour l'élève e1
e1.prenom = 'Tom'
# Afficher "L'élève e1 s'appelle Tom Machin" en utilisant ces attributs
print(f'L\'élève e1 s\'appelle {e1.prenom} {e1.nom}')
# Instancier un élève e2 de la classe Eleve
e2 = Eleve()
# Créer un attribut age ayant pour valeur 17 pour l'élève e2
e2.age = 17
# Créer un attribut classe ayant pour valeur 'T1' pour l'élève e2
e2.classe = 'T1'
# Afficher "L'élève e2 est en T1 et a 17 ans" en utilisant ces attributs
print(f'L\'élève e2 est en {e2.classe} et a {e2.age} ans')
Une fonction définie au sein d'une classe est appelée méthode.
La syntaxe pour définir une méthode d'instance utilise le mot-clé def
comme pour les fonctions.
Toute méthode d'instance prend obligatoirement un argument nommé self
. Il s'agit d'une référence vers l'instance créée. Ainsi, le premier argument d'une méthode d'instance est l'instance elle-même.
La syntaxe générale pour exécuter une méthode à l'extérieur de la classe est la suivante : nom_instance.nom_methode()
.
Cette syntaxe est équivalente à : NomClasse.nom_methode(nom_instance)
.
Reprenons l'exemple de la méthode lower
des objets de type str
. On peut écrire indifféremment :
'ABC'.lower() # est équivalent à : str.lower('ABC')
Exemple :
class ClasseBidon: '''Une classe qui ne fait pas grand-chose''' def affiche_bonjour(self): '''Méthode affichant "Bonjour !"''' print('Bonjour !') def eleve_au_carre(self, entier:int) -> int: ''' Cette méthode renvoie le carré d'un entier param entier : un nombre entier ''' return entier*entier # Instanciation d'un objet obj de la classe ClasseBidon obj = ClasseBidon() # Utilisations de la méthode affiche_bonjour obj.affiche_bonjour() ClasseBidon.affiche_bonjour(obj) # Utilisations de la méthode eleve_au_carre print(obj.eleve_au_carre(5)) print(ClasseBidon.eleve_au_carre(obj, 7))
Exemple n°2 :
On considère le code incomplet ci-dessous :
class Eleve: '''Classe représentant un élève''' def souffle_bougies(...): '''doc''' pass def change_classe(...): '''doc''' pass # Instancier un élève e de la classe Eleve ... # Créer un attribut age ayant pour valeur 17 pour l'élève e ... # Créer un attribut classe ayant pour valeur 'T1' pour l'élève e ... # Appliquer la méthode souffle_bougie à e et contrôler le résultat ... # Appliquer la méthode change_classe à e et contrôler le résultat ...
Q1. Compléter la signature et le corps de la méthode
souffle_bougies
.
Cette méthode doit :
- Incrémenter l'attribut
age
de 1 :- Afficher le message suivant : Joyeux anniv ! {nouvel âge} ans à présent
- Retourner la nouvelle valeur de l'attribut
age
Q2. Compléter la signature et le corps de la méthode
change_classe
.
Cette méthode prend en paramètre le nom de la nouvelle classenom_classe
et doit :
- Changer la valeur de l'attribut classe en conséquence
- Afficher le message suivant : Changement de classe : {nom de la classe actuelle} -> {nom de la nouvelle classe}
Q3. Écrire les instructions demandées.
__init__
¶Il est même très vivement recommandé de déclarer les attributs d'instances lors de l'instanciation d'un objet à partir d'une classe. On peut également souhaiter initialiser les valeurs de ces attributs.
Pour cela, on utilise une méthode spéciale appelée constructeur. Ce constructeur est implicitement exécuté lors de la création de chaque instance. Il se présente comme une méthode et suit la même syntaxe à ceci près que son nom est imposé : __init__
(double underscores). Hormis le premier paramètre, invariablement self
, il n’existe pas de contrainte concernant la liste des paramètres passés. En revanche, un constructeur ne doit pas retourner de résultat : Il ne peut donc pas contenir l'instruction return
.
Exemple : Reprenons l'exemple de la classe Eleve
class Eleve: def __init__(self, nom, prenom=''): self.nom = nom self.prenom = prenom e1 = Eleve('Machin') e2 = Eleve('Truc', 'Tim') print(f'L\'élève e1 a pour nom "{e1.nom}" et pour prénom "{e1.prenom}"') print(f'L\'élève e2 a pour nom "{e2.nom}" et pour prénom "{e2.prenom}"')
Dans cet exemple, on constate qu'il est possible de créer des instances de Eleve
de deux manières différentes :
nom
: Dans ce cas, on affecte à :nom
la valeur passée au paramètre nom
prenom
la valeur par défaut, c'est-à-dire la chaîne de caractère vide ''
nom
et prenom
: Dans ce cas, on affecte à :nom
la valeur passée au paramètre nom
prenom
la valeur passée au paramètre prenom
Exemple n°3 : On considère la classe Eleve
de l'exemple n°2
Q1. Créer le constructeur de la classe
Eleve
qui aura pour paramètresnom
,prenom
,age
etclasse
.
Ces paramètres prendront tous la valeur par défautNone
.
Q2. Créer la méthode
presentation
qui devra produire l'affichage suivant :Eleve : Nom : {son nom} Prénom : {son prenom} Age : {son age} ans Classe : {sa classe}
Q3. Modifier la méthode
souffle_bougies
de l'exemple précédent pour qu'elle afficheAge inconnu
si l'âge n'est pas renseigné.
Q4. Créer les élèves ci-dessous et tester les trois méthodes pour chacun d'eux :
- Un élève
e1
dont on ne connaît ni le nom, ni le prénom, ni l'âge ni la classe- Un élève
e2
âgé de 17 ans ayant pour nom 'Machin' et pour prénom 'Tom'- Un élève
e3
âgé de 18 ans ayant pour nom 'Truc' et pour prénom 'Tim', affecté à la classe 'T1'- Un élève
e4
affecté à la classe 'T1' et ayant pour prénom 'Bob'
Q5. Visualiser le code complété sur pythontutor.com.
class Eleve:
'''Classe représentant un élève'''
def __init__(self, nom=None, prenom=None, age=None, classe=None):
'''doc'''
self.nom = nom
self.prenom = prenom
self.age = age
self.classe = classe
def presentation(self):
'''doc'''
print(f'ELEVE :\
\nNom : {self.nom}\
\nPrénom : {self.prenom}\
\nAge : {self.age} ans\
\nClasse : {self.classe}')
def souffle_bougies(self):
'''doc'''
if self.age is not None:
self.age += 1
print(f'Joyeux Anniv ! {self.age} ans à présent.')
else:
print('Age inconnu')
return self.age
def change_classe(self, new_classe):
'''doc'''
print(f'Changement de classe : {self.classe} -> {new_classe}')
self.classe = new_classe
# Instancier un élève e1 de la classe Eleve :
e1 = Eleve()
# Présentation de l'élève e1 :
e1.presentation()
# Anniversaire de l'élève e1 :
e1.souffle_bougies()
# L'élève e1 passe en terminale 'T2' :
e1.change_classe('T2')
# Instancier un élève e2 de la classe Eleve :
e2 = Eleve('Machin', 'Tom', 17)
# Présentation de l'élève e2 :
e2.presentation()
# Anniversaire de l'élève e2 :
e2.souffle_bougies()
# L'élève e2 passe en terminale 'T2' :
e2.change_classe('T2')
# Instancier un élève e3 de la classe Eleve :
e3 = Eleve('Truc', 'Tim', 18, 'T1')
# Présentation de l'élève e3 :
e3.presentation()
# Anniversaire de l'élève e3 :
e3.souffle_bougies()
# L'élève e3 passe en terminale 'T2' :
e3.change_classe('T2')
# Instancier un élève e4 de la classe Eleve :
e4 = Eleve(prenom='Bob', classe='T1')
# Présentation de l'élève e4 :
e4.presentation()
# Anniversaire de l'élève e4 :
e4.souffle_bougies()
# L'élève e4 passe en terminale 'T2' :
e4.change_classe('T2')
↪ À Retenir : Structure générale d'une classe
class NomClasse: '''doc''' def __init__(self, param1, param2=val_defaut_2, param3=val_defaut_3): '''doc''' pass def methode_1(self): '''doc''' pass def methode_2(self, param1, param2): '''doc''' pass # instanciations d'objets de NomClasse i_1 = NomClasse(val1) i_2 = NomClasse(val1, val2) i_3 = NomClasse(val1, val2, val3) i_4 = NomClasse(param2=val2) # Utilisation des méthodes sur une instance i_3.methode_1() i_3.methode_2(val1, val2)
Eleve
Nous avons déjà rencontré un exemple de méthode spéciale avec la méthode __init__
. Une méthode spéciale, en Python, voit son nom entouré de part et d'autre par deux underscores.
Le nom d'une méthode spéciale prend donc la forme suivante : __nom_methode_speciale__
Parmi les méthodes spéciales existantes, nous allons nous intéresser à deux méthodes spéciales qui permettent de contrôler comment l'objet est représenté et affiché. En effet, si on essaye d'afficher directement un objet dans l'interpréteur ou grâce à la fonction print
, on obtient quelque chose d'assez laid comme le montre les exemples ci-dessous où e
est une instance de la classe Eleve
:
print(e)
affiche :<__main__.Eleve object at 0x000001DE30A60448>
e
dans l'interpréteur renvoie :<__main__.Eleve at 0x1de30a60448>
Les deux méthodes spéciales permettant de configurer la représentation et l'affichage des objets sont les suivantes :
__repr__
:
self
, et renvoie la chaîne de caractères que l'on souhaite afficher quand on saisit directement le nom de l'objet dans l'interpréteur.__str__
:
print
.
str
.On notera que, par défaut, si la méthode __str__
n'est pas définie, Python appelle la méthode __repr__
de l'objet.
Exemple n°4 :
Q1. Créer une représentation de la classe
Eleve
à l'aide de la méthode__repr__
.
Q2. Créer une représentation de la classe
Eleve
à l'aide de la méthode__str__
, différente de la représentation précédente.
Certains attributs sont créés de manière implicite lors l'instanciation d'un objet. Ils contiennent des informations sur l’instance.
Nom de l'attribut | Information |
---|---|
__module__ |
Contient le nom du module dans lequel est incluse la classe |
__class__ |
Contient le nom de la classe de l’instance. Ce nom est précédé du nom du module suivi d’un point. |
__dict__ |
Contient le dictionnaire des attributs de l’instance |
__doc__ |
Contient la documentation associée à la classe |
On peut souhaiter définir des attributs sur la classe elle-même, et non pas sur ses instances. On parle alors d'attribut de classe.
Ces attributs sont définis en dehors des méthodes de la classe.
Voici la syntaxe pour accéder en lecture ou en écriture à ses attributs :
NomClasse.nom_attribut_classe
à l'extérieur de la classecls.nom_attribut_classe
depuis la classeIl est également possible de définir des méthodes de classe.
Le premier argument d'une telle méthode sera la classe elle-même, représentée par le mot-clé cls
.
Le décorateur @classmethod
devra précéder la définition de la fonction.
Exemple :
class Citron: forme = "ellipsoïde" # attribut de classe saveur = "acide" # attribut de classe def __init__(self, couleur="jaune", taille="standard", masse=100): self.couleur = couleur # attribut d'instance self.taille = taille # attribut d'instance self.masse = masse # attribut d'instance (masse en gramme) def affiche_attributs_instance(self): # méthode d'instance print(f'Attribut couleur de l\'instance : {self.couleur}\ \nAttribut taille de l\'instance : {self.taille}\ \nAttribut masse de l\'instance : {self.masse} g\n') @classmethod def affiche_attributs_classe(cls): # méthode de classe print(f'Attribut forme de la classe Citron : {cls.forme}\ \nAttribut saveur de la classe Citron : {cls.saveur}\n') c1 = Citron() c1.affiche_attributs_instance() c2 = Citron("vert", "XXL", 150) c2.affiche_attributs_instance() Citron.affiche_attributs_classe()
Exemple n°5 :
On considère la classe
Eleve
ci-dessous :class Eleve: '''Classe représentant un élève''' def __init__(self, nom=None, prenom=None, age=None, classe=None): '''Constructeur''' self.nom = nom self.prenom = prenom self.age = age self.classe = classe
Q1. Initialiser un attribut de classe
compteur
à zéro.
Cet attribut devra être incrémenté de 1 à la création de chaque nouvelle instance deEleve
.
Q2. Créer une méthode de classe
combien
qui retourne le nombre d'objets de la classeEleve
créés, puis la tester.
class Eleve:
'''Classe représentant un élève'''
compteur = 0
def __init__(self, nom=None, prenom=None, age=None, classe=None):
'''Constructeur'''
self.nom = nom
self.prenom = prenom
self.age = age
self.classe = classe
Eleve.compteur += 1
@classmethod
def combien(cls):
'''doc'''
return cls.compteur
# Instancier un élève e1 de la classe Eleve :
e1 = Eleve()
e2 = Eleve('Machin', 'Tom', 17)
e3 = Eleve('Truc', 'Tim', 18, 'T1')
e4 = Eleve(prenom='Bob', classe='T1')
Eleve.combien()
Une interface est une description des capacités fournies par une unité de code. Dans les langages orientés objet comme Python, les interfaces sont souvent définies par une collection de signatures de méthodes qui doivent être fournies par une classe : Les méthodes y sont seulement déclarées. Cela permet de définir un ensemble de services visibles depuis l’extérieur (l’API : Application Programming Interface). Une classe qui implémente une interface doit obligatoirement implémenter chacune des méthodes déclarées dans l’interface
Les interfaces sont donc utiles pour spécifier une sorte de cahier des charges. En marquant qu'un type implémente une interface, nous donnons une garantie, vérifiable par un programme, que l'implémentation fournit les méthodes spécifiées par l'interface, sans nous préoccuper de la façon dont ces services sont réellement implémentés.
On notera enfin qu'une classe peut implémenter plusieurs interfaces.
Dans ce cours, nous utiliserons la bibliothèque interface
pour déclarer des interfaces et créer des classes implémentant ces interfaces. Les interfaces créées à l'aide de cette bibliothèque nous permettront de détecter les erreurs suivantes :
Exemple :
Voici sur un exemple la syntaxe pour créer une interface et une classe implémentant cette interface.
# import de la classe Interface et de la fonction implements from interface import implements, Interface # Définition de l'interface InterfaceOperations class InterfaceOperations(Interface): def somme(self, x: int, y: int) -> int: pass def produit(self, x: int, y: int) -> int: pass # Création de la classe Operations implémentant l'interface InterfaceOperations class Operations(implements(InterfaceOperations)): def somme(self, x: int, y: int) -> int: return x + y def produit(self, x: int, y: int) -> int: return x * y
Exemple n°6 : Classe implémentant une interface
On considère l'interface
InterfaceEleve
ci-dessous.from interface import Interface, implements class InterfaceEleve(Interface): '''Classe représentant un élève''' def __init__(self, nom: str = None, prenom: str = None, age: int = None, classe: str = None): '''Constructeur - initialisation des attributs d'instance''' pass def presentation(self): ''' Méthode affichant les attributs de l'élève sous la forme suivante : Eleve : Nom : {son nom} Prénom : {son prenom} Age : {son age} ans Classe : {sa classe} ''' pass def souffle_bougies(self) -> int: ''' Méthode qui incrémente l'âge de 1 et renvoie la nouvelle valeur s'il est renseigné. Sinon, elle affiche 'âge inconnu' et renvoie None ''' pass def change_classe(self, new_classe: str): '''Méthode changeant la valeur de l'attribut classe par new_classe''' pass
Q1. Créer la classe
Eleve
implémentant cette interface en vous aidant de l'exemple n°3.
Q2. Modifier la signature d'une fonction de la classe
Eleve
. Que se passe-t-il ?
Q3. Commenter la fonction
change_classe
. Que se passe-t-il ?
Exemple n°7 : Différentes implémentations pour une même interface
Q1. Créer une classe
OperationsV2
qui :
- Implémente l'interface
InterfaceOperations
- Calcule le produit de deux entiers de manière itérative en utilisant uniquement l'opérateur
+
from interface import implements, Interface
# Définition de l'interface InterfaceOperations
class InterfaceOperations(Interface):
def somme(self, x: int, y: int) -> int:
pass
def produit(self, x: int, y: int) -> int:
pass
# Création de la classe OperationsV2 implémentant l'interface InterfaceOperations
class OperationsV2(implements(InterfaceOperations)):
def somme(self, x: int, y: int) -> int:
return x + y
def produit(self, x: int, y: int) -> int:
res = 0
for i in range(x):
res += y
return res
Q2. Créer une classe
OperationsV3
qui :
- Implémente l'interface
InterfaceOperations
- Calcule le produit de deux entiers de manière récursive comme indiqué ci-dessous : $produit(x, y) = \left\{\begin{array}{lll}0 & \text{si }x=0\text{ ou }y=0 \\ y & \text{si }x=1 \\ y + produit(x-1, y) & \text{sinon}\end{array}\right.$
# Création de la classe OperationsV3 implémentant l'interface InterfaceOperations
class OperationsV3(implements(InterfaceOperations)):
def somme(self, x: int, y: int) -> int:
return x + y
def produit(self, x: int, y: int) -> int:
if x == 0 or y == 0:
return 0
if x == 1:
return y
return y + produit(x - 1, y)
Exemple n°8 : Classes implémentant plusieurs interfaces
On considère les trois interfaces ci-dessous :
class InterfaceFigure(Interface): '''Interface pour créer des classes de figures géométriques en 2D''' def perimetre(self) -> float: pass def aire(self) -> float: pass def attributs(self) -> dict: '''Renvoie le dictionnaire des attributs de la figure''' pass
class InterfaceDisque(Interface): '''Interface pour créer des classes de disques''' def __init__(self, centre: tuple = (0., 0.), rayon: int = None): '''constructeur''' pass def diametre(self) -> float: '''renvoie le diamètre du cercle''' pass
class InterfaceRectangle(Interface): '''Interface pour créer des classes de rectangles''' def __init__(self, sommet_1: tuple = (0., 0.), longueur: int = None, largeur: int = None): '''constructeur''' pass def est_carre(self) -> bool: '''renvoie True si c'est un carré et False sinon''' pass def liste_sommets(self) -> list: ''' renvoie la liste des tuples représentant les 4 sommets Les deux premiers sommets seront sommet_1 et (x_1 + longueur, y_1) Les sommets seront créés dans le sens direct ''' pass
Q1. Créer une classe
Disque
implémentant les interfacesInterfaceDisque
etInterfaceFigure
.
Ne pas oublier d'importer la constantepi
de la librairiemath
.
Tester le code avec :
- Une instance
d1
utilisant les valeurs par défaut- Une instance
d2
représentant un disque de centre(0., 0.)
et de rayon5
from interface import Interface, implements
class InterfaceFigure(Interface):
'''Interface pour créer des classes de figures géométriques en 2D'''
def perimetre(self) -> float:
pass
def aire(self) -> float:
pass
def attributs(self) -> dict:
'''Renvoie le dictionnaire des attributs de la figure'''
pass
class InterfaceDisque(Interface):
'''Interface pour créer des classes de disques'''
def __init__(self,
centre: tuple = (0., 0.),
rayon: int = None):
'''constructeur'''
pass
def diametre(self) -> float:
'''renvoie le diamètre du cercle'''
pass
class InterfaceRectangle(Interface):
'''Interface pour créer des classes de rectangles'''
def __init__(self,
sommet_1: tuple = (0., 0.),
longueur: int = None,
largeur: int = None):
'''constructeur'''
pass
def est_carre(self) -> bool:
'''renvoie True si c'est un carré et False sinon'''
pass
def liste_sommets(self) -> list:
'''
renvoie la liste des tuples représentant les 4 sommets
Les deux premiers sommets seront sommet_1 et (x_1 + longueur, y_1)
Les sommets seront créés dans le sens direct
'''
pass
import math
class Disque(implements(InterfaceDisque, InterfaceFigure)):
'''Classe implémentant InterfaceDisque et InterfaceFigure'''
def __init__(self,
centre: tuple = (0., 0.),
rayon: int = None):
'''constructeur'''
self.centre = centre
self.rayon = rayon
def diametre(self) -> float:
'''renvoie le diamètre du cercle'''
if self.rayon is None:
return None
return 2*self.rayon
def perimetre(self) -> float:
'''renvoie le périmètre du disque'''
if self.rayon is not None:
return 2 * math.pi * self.rayon
def aire(self) -> float:
'''renvoie l'aire du disque'''
if self.rayon is not None:
return math.pi * self.rayon ** 2
def attributs(self) -> dict:
'''Renvoie le dictionnaire des attributs de la figure'''
return {'centre': self.centre, 'rayon': self.rayon}
d1 = Disque()
print(d1.attributs())
print(d1.aire())
print(d1.perimetre())
print(d1.diametre())
d2 = Disque((2., 3.), 5)
print(d2.attributs())
print(d2.aire())
print(d2.perimetre())
print(d2.diametre())
Q2. Créer une classe
Rectangle
implémentant les interfacesInterfaceRectangle
etInterfaceFigure
.
Tester le code avec :
- Une instance
r1
utilisant les valeurs par défaut- Une instance
r2
représentant un rectangle de dimension4 × 3
et ayant pour sommet le point(2., 1.)
class Rectangle(implements(InterfaceRectangle, InterfaceFigure)):
'''Classe implémentant InterfaceRectangle et InterfaceFigure'''
def __init__(self,
sommet_1: tuple = (0., 0.),
longueur: int = None,
largeur: int = None):
'''constructeur'''
self.sommet_1 = sommet_1
self.longueur = longueur
self.largeur = largeur
def est_carre(self) -> bool:
'''renvoie True si c'est un carré et False sinon'''
if self.longueur is not None and self.largeur is not None:
return self.longueur == self.largeur
def liste_sommets(self) -> list:
'''
renvoie la liste des tuples représentant les 4 sommets
Les deux premiers sommets seront sommet_1 et (x_1 + longueur, y_1)
Les sommets seront créés dans le sens direct
'''
if self.longueur is not None and self.largeur is not None:
xs1, ys1 = self.sommet_1
return [(xs1, ys1),
(xs1 + self.longueur, ys1),
(xs1 + self.longueur, ys1 + self.largeur),
(xs1, ys1 + self.largeur)]
def perimetre(self) -> float:
'''renvoie le périmètre du rectangle'''
if self.longueur is not None and self.largeur is not None:
return 2 * (self.longueur + self.largeur)
def aire(self) -> float:
'''renvoie l'aire du rectangle'''
if self.longueur is not None and self.largeur is not None:
return self.longueur * self.largeur
def attributs(self) -> dict:
'''Renvoie le dictionnaire des attributs de la figure'''
return {'longueur': self.longueur, 'largeur': self.largeur}
r1 = Rectangle()
print(r1.attributs())
print(r1.aire())
print(r1.perimetre())
print(r1.est_carre())
print(r1.liste_sommets())
r2 = Rectangle(longueur=4, largeur=3)
print(r2.attributs())
print(r2.aire())
print(r2.perimetre())
print(r2.est_carre())
print(r2.liste_sommets())
Les structures de données organisent le stockage dans des ordinateurs afin que nous puissions accéder et modifier efficacement les données.
Les listes chaînées, les piles et les files font partie des premières structures de données définies en informatique.
Définition :
Une liste chaînée est une structure de données très utilisée en informatique. Elle est fondamentale pour les structures de niveau supérieur comme les piles ou les files.
D'une manière générale, une liste chaînée est une collection d'éléments de données qui sont connectés par des références. Habituellement, chaque élément de la liste chaînée a le même type de données qui est spécifique à la liste.
Un élément d'une telle liste est appelé un noeud. À la différence des tableaux qui sont stockés séquentiellement en mémoire, les noeuds peuvent se trouver sur différents segments de mémoire : On passe d'un noeud à son successeur en suivant les références. La fin de la liste est marquée avec l'élément
NULL
(qui correspond àNone
en Python).
Liste des opérations que l'on peut effectuer sur une liste chaînée :
- Créer une nouvelle liste vide
- Créer une nouvelle liste à partir d'une donnée et d'une autre liste
- Tester si une liste est vide
- Ajouter un noeud en tête de liste
- Supprimer le noeud en tête de liste et renvoyer sa donnée
- Compter et renvoyer le nombre de noeuds d'une liste (c'est-à-dire le nombre de données)
Interface d'une liste chaînée en Python :
class InterfaceListeChainee(Interface): def __init__(self, donnee=None, liste_chainee=None): ''' Constructeur permettant de créer une nouvelle liste. Cette liste peut être : - La liste vide - La liste ayant pour tête le noeud contenant donnee et pour queue liste_chainee param donnee : Donnée à ajouter en tête de liste param liste_chainee : Liste représentant la queue de nouvelle liste ''' pass def est_vide(self) -> bool: ''' Méthode permettant de tester si la liste est vide return : True si la liste est vide, False sinon ''' pass def ajoute_en_tete(self, donnee): ''' Méthode permettant d'ajouter un noeud en tête de liste param donnee : donnée du noeud à ajouter ''' pass def suppr_en_tete(self): ''' Méthode permettant de supprimer le noeud en tête de liste et qui renvoie la donnée correspondante return : donnée du noeud en tête ''' pass def longueur(self) -> int: ''' Méthode qui renvoie la longueur de la liste chaînée return : nombre de données dans la liste ''' pass
Exemple n°9 :
On suppose que la classe
LinkedList
implémente l'interfaceInterfaceListeChainee
.
Expliquer chacune des lignes de cette suite d'instructions.
On précisera le contenu des listes et variables créées à chaque étape.l = LinkedList() print(l.est_vide()) l1 = LinkedList(2) print(l1.est_vide()) l1.ajoute_en_tete(3) l1.ajoute_en_tete(5) l1.ajoute_en_tete(8) t = suppr_en_tete() l2 = LinkedList(9, l1) c2 = l2.longueur() l3 = LinkedList(15, LinkedList(4, LinkedList(7, LinkedList(6)))) c3 = l3.longueur()
Les piles, comme son nom l'indique, suivent le principe du dernier entré, premier sorti : LIFO pour Last In First Out. Il est donc uniquement possible de manipuler le dernier élément introduit dans la pile. On prend souvent l'analogie avec une pile d'assiettes : Dans une telle pile, la seule assiette directement accessible est la dernière assiette qui a été déposée sur la pile ...
Voici quelques exemples d'applications :
Liste des opérations que l'on peut effectuer sur une pile :
- Créer une pile vide
- Tester si une pile est vide
- Empiler un nouvel élément sur la pile (
push
)- Renvoyer l'élément au sommet de la pile tout en le supprimant (
pop
)- Renvoyer l'élément situé au sommet de la pile sans le supprimer de la pile (
top
)- Renvoyer le nombre d'éléments présents dans la pile (
size
)
Interface d'une pile en Python :
class InterfacePile(Interface):
def __init__(self):
'''Constructeur permettant la création d'une pile vide'''
pass
def is_empty(self) -> bool:
'''
Méthode permettant de tester si la pile est vide
return : True si la pile est vide, False sinon
'''
pass
def push(self, donnee):
'''
Méthode permettant d'empiler donnee sur la pile
param donnee : donnée à empiler
'''
pass
def pop(self):
'''
Méthode permettant de dépiler l'élément en haut de la pile
return : donnée en haut de la pile
'''
pass
def top(self):
'''
Méthode permettant d'accéder à l'élément en haut de la pile mais sans le supprimer
return : donnée en haut de la pile
'''
pass
def size(self) -> int:
'''
Méthode renvoyant le nombre d'éléments de la pile
return : nombre d'élément de la pile
'''
pass
Exemple n°10 :
Q1. On suppose que la classe
Pile
implémente l'interfaceInterfacePile
.
Expliquer chacune des lignes de cette suite d'instructions.
On précisera le contenu de la pile à chaque étape.p = Pile() p.push(5) p.push(6) p.push(7) p.push(10) print(p.is_empty()) print(p.top()) print(p.size()) print(p.pop()) print(p.is_empty()) print(p.top()) print(p.size()) print(p.pop()) print(p.pop()) print(p.pop()) print(p.is_empty()) print(p.top()) print(p.size())
Q2. Créer une classe
Pile
implémentant l'interfaceInterfacePile
en utilisant le typelist
On veillera à respecter les contraintes suivantes :
- Les méthodes
pop
ettop
renverrontNone
si la pile est vide- On ajoutera à cette classe une méthode
display
permettant d'afficher les éléments de la pile
Exemple : L'affichage produit pour la pile12, 4, 8
où8
est le sommet sera le suivant :8 4 12
from interface import Interface, implements
class InterfacePile(Interface):
def __init__(self):
'''Constructeur permettant la création d'une pile vide'''
pass
def is_empty(self) -> bool:
'''
Méthode permettant de tester si la pile est vide
return : True si la pile est vide, False sinon
'''
pass
def push(self, donnee):
'''
Méthode permettant d'empiler donnee sur la pile
param donnee : donnée à empiler
'''
pass
def pop(self):
'''
Méthode permettant de dépiler l'élément en haut de la pile
return : donnée en haut de la pile
'''
pass
def top(self):
'''
Méthode permettant d'accéder à l'élément en haut de la pile mais sans le supprimer
return : donnée en haut de la pile
'''
pass
def size(self) -> int:
'''
Méthode renvoyant le nombre d'éléments de la pile
return : nombre d'élément de la pile
'''
pass
class Pile(implements(InterfacePile)):
def __init__(self):
self.liste = []
def is_empty(self) -> bool:
return len(self.liste) == 0
def push(self, donnee):
self.liste.append(donnee)
def pop(self):
if len(self.liste) == 0:
return None
return self.liste.pop()
def top(self):
if len(self.liste) == 0:
return None
return self.liste[-1]
def size(self):
return len(self.liste)
def display(self):
if len(self.liste) == 0:
print('None')
else:
res = ''
for donnee in reversed(self.liste):
res += str(donnee) + '\n'
print('Affichage pile :', res, sep='\n')
Q3. Tester le bon fonctionnement des méthodes de la classe
Pile
sur les instructions de la question 1.
p = Pile()
p.push(5)
p.push(6)
p.push(7)
p.push(10)
p.display()
print('Pile vide ?', p.is_empty())
print('Sommet :', p.top())
print('Taille :', p.size())
print('On dépile :', p.pop())
p.display()
print('Pile vide ?', p.is_empty())
print('Sommet :', p.top())
print('Taille :', p.size())
print('On dépile :', p.pop())
print('On dépile :', p.pop())
print('On dépile :', p.pop())
p.display()
print('Pile vide ?', p.is_empty())
print('Sommet :', p.top())
print('Taille :', p.size())
Les files d'attente, comme son nom l'indique, suivent le principe du premier entré, premier sorti : FIFO pour First In First Out. Dans une file on ajoute donc des éléments à une extrémité de la file et on supprime des éléments à l'autre extrémité. On prend prendre l'analogie de la file d'attente pour les billets de cinéma : le premier à faire la queue est le premier à acheter un billet et à profiter du film.
En général, cette structure est utilisée pour mémoriser temporairement des transactions qui doivent attendre pour être traitées, comme pour les serveurs d'impression qui traitent les requêtes dans l'ordre dans lequel elles arrivent, et les insèrent dans une file d'attente. Un algorithme de parcours en largeur utilise également une file pour mémoriser les noeuds visités.
Liste des opérations que l'on peut effectuer sur une file :
- Créer une file vide
- Tester si une file est vide
- Ajouter un nouvel élément à la file (
enqueue
)- Renvoyer l'élément en début de file tout en le supprimant (
dequeue
)- Renvoyer l'élément en début de file sans le supprimer de la file (
first
)- Renvoyer le nombre d'éléments présents dans la file (
size
)
Interface d'une file en Python :
class InterfaceFile(Interface):
def __init__(self):
'''Constructeur permettant la création d'une file vide'''
pass
def is_empty(self) -> bool:
'''
Méthode permettant de tester si la file est vide
return : True si la file est vide, False sinon
'''
pass
def enqueue(self, donnee):
'''
Méthode permettant d'ajouter donnee en fin de file
param donnee : donnée à ajouter
'''
pass
def dequeue(self):
'''
Méthode qui supprime et renvoie le premier élément de la file
return : donnée en début de file
'''
pass
def first(self):
'''
Méthode permettant d'accéder l'élément en début de file mais sans le supprimer
return : donnée en début de file
'''
pass
def size(self) -> int:
'''
Méthode renvoyant le nombre d'éléments de la file
return : nombre d'éléments de la file
'''
pass
Exemple n°11 :
Q1. On suppose que la classe
File
implémente l'interfaceInterfaceFile
.
Expliquer chacune des lignes de cette suite d'instructions.
On précisera le contenu de la file à chaque étape.f = File() f.enqueue(5) f.enqueue(6) f.enqueue(7) f.enqueue(10) print(f.is_empty()) print(f.first()) print(f.size()) print(f.dequeue()) print(f.is_empty()) print(f.first()) print(f.size()) print(f.dequeue()) print(f.dequeue()) print(f.dequeue()) print(f.is_empty()) print(f.first()) print(f.size())
Q2. Créer une classe
File
implémentant l'interfaceInterfaceFile
en utilisant le typelist
. On veillera à respecter les contraintes suivantes :
- Les méthodes
dequeue
etfirst
renverrontNone
si la file est vide- On ajoutera à cette classe une méthode
display
permettant d'afficher les éléments de la file
Exemple : L'affichage produit pour la file12, 4, 8
où12
est le premier élément sera le suivant :8 4 12
from interface import Interface, implements
class InterfaceFile(Interface):
def __init__(self):
'''Constructeur permettant la création d'une file vide'''
pass
def is_empty(self) -> bool:
'''
Méthode permettant de tester si la file est vide
return : True si la file est vide, False sinon
'''
pass
def enqueue(self, donnee):
'''
Méthode permettant d'ajouter donnee en fin de file
param donnee : donnée à ajouter
'''
pass
def dequeue(self):
'''
Méthode qui supprime et renvoie le premier élément de la file
return : donnée en début de file
'''
pass
def first(self):
'''
Méthode permettant d'accéder l'élément en début de file mais sans le supprimer
return : donnée en début de file
'''
pass
def size(self) -> int:
'''
Méthode renvoyant le nombre d'éléments de la file
return : nombre d'éléments de la file
'''
pass
class File(implements(InterfaceFile)):
def __init__(self):
self.liste = []
def is_empty(self) -> bool:
return len(self.liste) == 0
def enqueue(self, donnee):
self.liste.append(donnee)
def dequeue(self):
if len(self.liste) == 0:
return None
return self.liste.pop(0)
def first(self):
if len(self.liste) == 0:
return None
return self.liste[0]
def size(self) -> int:
return len(self.liste)
def display(self):
if len(self.liste) == 0:
print('None')
else:
res = ''
for donnee in reversed(self.liste):
res += str(donnee) + ' '
print(res)
Q3. Tester le bon fonctionnement des méthodes de la classe
File
sur les instructions de la question 1.
f = File()
f.enqueue(5)
f.enqueue(6)
f.enqueue(7)
f.enqueue(10)
f.display()
print('File vide ?', f.is_empty())
print('Premier :', f.first())
print('Taille :', f.size())
print('On défile :', f.dequeue())
f.display()
print('File vide ?', f.is_empty())
print('Premier :', f.first())
print('Taille :', f.size())
print('On défile :', f.dequeue())
print('On défile :', f.dequeue())
print('On défile :', f.dequeue())
f.display()
print('File vide ?', f.is_empty())
print('Premier :', f.first())
print('Taille :', f.size())