© Your Copyright
Cette partie de cours introduit les notions de classe, d’objet, d’attribut, de méthode et d’initialisateur. A la fin du travail proposé vous saurez:
- distinguer les notions de classe et d’objet
- définir une classe simple avec des états et des comportements.
- instancier des objets
- modifier les attributs d’un objet
- invoquer des méthodes
- détruire des instances
La programmation orientée objet (POO) peut être vue comme un paradigme de programmation qui ajoute une couche d’abstraction au paradigme de la programmation procédurale. Elle insiste sur le principe d’encapsulation déjà présent en programmation procédurale et introduit des notions comme l’héritage, les interfaces et le polymorphisme dynamique.
La POO est mise en œuvre par une grande variété de langages. Notons que certains de ces langages permettent d’écrire du code procédural sans avoir nécessairement recours au paradigme objet. C’est la raison pour laquelle le langage multi-paradigmes python a pu être utilisé pour introduire les concepts de la programmation procédurale aux semestre 1 et 2 de l’ENIB. De plus, la POO fournit des outils de modélisation (UML) et de génie logiciel très répandus. Elle est largement utilisée depuis des décennies pour coder toute sorte d’applications informatiques; au point qu’il est aujourd’hui indispensable pour un ingénieur généraliste d’en comprendre les concepts principaux.
A cette étape, “le respect des traditions et l’hommage rendu aux anciens” sera la principale justification de cette introduction à la POO. Considérons que vous devez maîtriser la POO car c’est ce qui est utilisé par vos prédécesseurs! Lorsque que vous aurez construit les notions de classes et d’objets, il vous sera possible d’aborder les notions plus spécifiques qui justifient la POO.
Pour entrer progressivement dans ce nouveau paradigme, nous allons construire la notion d’objet et de classe dans le paradigme procédural avec le langage python. Ensuite nous reformulerons le code avec la syntaxe appropriée pour définir et instancier des classes simples avec le langage python.
Un objet est un conteneur qui contient des informations et des mécanismes. Pour l’illustrer nous pouvons essayer de construire un objet avec un type de donnée python que vous connaissez déjà : le dictionnaire.
Dictionnaire
Un dictionnaire python est un conteneur qui associe des données à une clé. Le dictionnaire appartient à la famille des tables de hachage. La clé est souvent une chaîne de caractères mais peut être une autre donnée hachable (entier, réel, N-uplet).
my_dictionnary=dict()
my_dictionnary["key"] = "data"
my_dictionnary["another key"] = 12
#print "data"
print(my_dictionnary["key"])
#print 12
print(my_dictionnary["another key"])
Les informations contenues dans l’objet représentent son état. Par exemple, nous pouvons créer un objet qui représente un point de couleur et qui contient des informations relatives à sa position et sa couleur :
my_colored_point
est notre objet. Jusqu’ici, il n’y rien de très original, nous manipulons un conteneur qui contient des informations.
En langage C, vous auriez pu utiliser une structure de données pour coder quelque chose d’équivalent.
Nous l’avons énoncé précédemment, un objet contient aussi des mécanismes.
L’idée ici, c’est d’embarquer à l’intérieur de l’objet des références vers des fonctions qui savent manipuler cet objet.
Nous parlerons alors de fonctions membres qui dotent l’objet d’un comportement.
Par exemple, nous imaginons que le point peut se déplacer vers la gauche et s’afficher. Nous créons alors une fonction show
et une fonction move_left
. Puis, nous plaçons une référence vers ces deux fonctions dans colored_point
:
Nous remarquons, l’utilisation du paramètre self
dans les fonctions move_left()
et show()
.
Lorsque que les fonctions sont appelées pour manipuler l’objet, elle doivent nécessairement recevoir une référence sur l’objet.
Ici, le paramètre self
prendra la valeur de my_colored_point
à l’appel de la fonction.
Ce paramètre self
aurait pu être nommé différemment, mais les développeurs python ont pris pour habitude de nommer ce paramètre self
.
Cette habitude est tellement intégrée, que la plupart des éditeurs python propose une coloration syntaxique particulière pour le mot self
.
Dans l’ENIBook, self
apparaît en bleu.
Nous avons embarqué les fonctions qui savent manipuler l’objet “dans” l’objet lui même. Quel intérêt cela peut-il bien avoir? Nous le découvrirons plus précisément lorsque nous évoquerons le polymorphisme dynamique mais nous pouvons déjà nous en faire une idée.
Si dans un même programme nous devons manipuler une collection d’objets-dictionnaires hétérogènes qui ont des comportements d’affichage différents, rien de plus simple :
for obj in objects_list:
obj['show'](obj)
Ce simple code demande à chacun des objets de s’afficher et chaque objet à la responsabilité de savoir quelle fonction membre appeler. obj['show']
peut référencer une fonction différente à chaque passage dans la boucle et cela fonctionne très bien du moment qu’on s’assure que chaque objet-dictionnaire possède bien une référence vers une fonction à la clé 'show'
.
Si les objets n’embarquaient pas leur fonctions, il serait impossible de faire de tels appels automatiques et il faudrait appeler spécifiquement pour chaque objet la bonne fonction d’affichage.
Nous avons créé un objet my_colored_point
qui contient 3 variables membres (x
, y
, color
) et deux fonctions membres (move_left()
, show()
).
Maintenant nous souhaitons créer et manipuler plusieurs points colorés.
Pour cela, il suffit de recopier le code de construction de my_colored_point
autant de fois que nous souhaitons d’objets... ou mieux encore, de mettre ce code dans une fonction que nous appellerons à chaque création de point. Cette fonction est un constructeur :
En fait, le rôle joué ici par notre constructeur est équivalent à la notion de classe dans la vision orientée objet. Le constructeur définit en quelque sorte un nouveau type d’objet “colored_point” et cette définition est ce qu’on appelle une classe.
red_point
etblack_point
sont alors deux objets de la classecolored_point
.x
,y
,color
sont des attributs de la classecolored_point
.show
etmove_left
sont des méthodes descolored_point
.
On voit comment une classe définit les propriétés communes à un ensemble d’objets, ici des points de couleur.
Comment réécrire le code procédural précédent avec la syntaxe objet python appropriée?
Nous souhaitons définir une classe ColoredPoint
et instancier des objets tels red_point
et black_point
de l’exemple précédent.
D’abord nous utilisons le mot clé class
pour créer un nouveau type d’objet:
#---- class definition ----
class ColoredPoint:
pass
class
définit notamment un espace de nommage (namespace
) dans lequel nous pourrons ajouter des informations qui définissent la classe.
Le mot anglais “instance” peut se traduire par “cas” ou “exemple”.
Le verbe “instancier” en informatique, signifie créer un cas particulier à partir de la définition abstraite que contient une classe.
Ainsi, nous pouvons instancier un nouvel objet de type ColoredPoint
de la manière suivante :
#---- using ----
my_point = ColoredPoint()
A cette étape my_point
ne possède pas d’état ni de comportement, mais il a déjà une identité.
L’exécution du code suivant nous indique que my_point
est une instance de la classe ColoredPoint
.
L’exécution de ce même code en dehors de l’ENIBook avec un vrai interpréteur python nous indique que my_point
est une instance de la classe ColoredPoint
qui occupe une certaine zone de la mémoire du programme :
<__main__.ColoredPoint object at 0x10fcbdfc8>
Nous avons définit le comportement des objets en définissant des fonctions membres. Le terme de méthode est également utilisé pour désigner une fonction membre d’une classe.
Ajoutons maintenant un comportement à la classe en dotant la classe d’une méthode :
#defines function
def show(self):
print(self)
#adds method to ColoredPoint namespace
ColoredPoint.show=show
L’appel de la méthode :
#invokes method
my_point.show()
Ou plus simplement, nous pouvons directement définir la fonction membre dans la définition de l’espace de nommage de la classe :
#---- class definition ----
class ColoredPoint:
def show(self):
print(self)
my_point = ColoredPoint()
my_point.show()
(n’hésitez pas tester les briques de code dans l’ENIBook ci-dessus, ou mieux encore, avec votre interpréteur python)
Pour définir l’état des objets, autrement dit leurs attributs x
, y
et color
, nous utiliserons un constructeur.
#---- class definition ----
class ColoredPoint:
def __init__(self):
self.x=0.
self.y=0.
self.color="black"
def show(self):
print(self)
my_point = ColoredPoint()
my_point.show()
Un constructeur est une fonction qui porte le nom __init__
.
Il prends au moins un paramètre self
qui sera une références sur l’objet à initialiser.
Le constructeur est appelé automatiquement par python immédiatement après la création d’un nouvel objet.
Pour être précis, en python le concept de constructeur se décline en deux fonctions : __init__
et __new__
.
La documentation nomme __new__
le constructeur et __init__
l’initializer .
Nous n’aborderons pas dans ce cours l’utilisation de __new__
(utilisé par exemple pour l’extension des types non mutables).
La fonction __init__
correspond bien à la définition d’un constructeur dans les autres langages de programmation orientée objet.
Cependant, il est vrai que le terme initializer semble mieux correspondre au rôle de cette fonction car la “construction” de l’objet est préalable à l’appel du constructeur.
Finalement, par abus de langage du point de vue de python, nous utiliserons indifféremment constructeur ou initializer pour parler de la fonction __init__
.
Il est possible de paramétrer la fonction __init__()
pour donner à la création de l’objet des informations qui permettent de déterminer l’état initial de l’objet :
class ColoredPoint:
def __init__(self, x=0., y=0., color='black'):
self.x=x
self.y=y
self.color=color
def show(self):
print(self)
my_point = ColoredPoint(12.0,6.0,'red')
my_point.show()
Souvent, à chaque attribut correspond un paramètre du constructeur, mais ce n’est pas nécessairement le cas. Parfois, l’initialisation de certains attributs se fait sans paramètre (comme pour le premier exemple de constructeur) et parfois certains paramètres ne sont pas directement utilisés pour initialiser un attribut.
Une fois que l’attribut a été crée, pour y accéder, il suffit de faire précéder le nom de l’attribut par une référence sur l’objet et par un point :
my_point.x = my_point.x+1
self.x = self.x+1
On notera que les méthodes peuvent également prendre des paramètres d’entrée en plus du self
:
class ColoredPoint:
def move_left(self,n=1.0):
self.x = self.x-n
pour appeler la méthode il suffit de faire précéder l’appel de la méthode par la référence vers l’objet et par un point :
my_colored_point.move_left(5.0)
self.move_left(5.0)
Vous pouvez tester ce code et vous essayer à quelques modifications pour vérifier que vous savez créer de nouveaux objets, de nouvelles méthodes, etc...
self
est le premier paramètre de toutes les fonctions membres.
La présence de ce paramètre est indispensable.
Son nom est self
par convention.
Il est fortement conseillé d’appliquer cette convention.
Pour bien comprendre ce qu’il se passe il faut savoir que les deux instructions suivantes sont (pratiquement) équivalentes
my_point.show()
ColoredPoint.show(my_point)
En fait, lorsque python interprète la première expression, il la traduit en la deuxième.
Lorsqu’on n’utilise plus un objet, il s’agit de libérer la mémoire alloué pour cet objet. Pour cela il faut détruire l’objet.
Dans certains langages orientés objet, il est possible de détruire explicitement un objet.
Dans le langage python, il est impossible de détruire soi-même un objet. On a recours à l’utilisation d’un ramasse miettes qui détruit les objet pour nous.
Le ramasse miettes ou garbage collector est un programme qui détruit les objets qui ne sont plus référencés. Ainsi, pour détruire un objet il suffit de l’oublier... autrement dit, le déréférencer
#creation
obj = MyClass()
#dereferencing
obj = None
En python, toutes les variables sont en réalité des références vers des données. Pendant l’exécution du code, un mécanisme interne à python est capable de compter le nombre de références existantes vers une même donnée. Le ramasse miettes utilise ce mécanisme. Si le compte tombe à 0, alors la mémoire est libérée. Quand une donnée est finalement détruite, une fonction spéciale est automatiquement appelée pour éventuellement réaliser des traitement spécifiques. Il s’agit du finalizer. Pour nous le finalizer servira à comprendre le fonctionnement du ramasse miette en affichant un message à chaque destruction d’objet.
La finalizer ou destructeur se code en ajoutant la fonction __del__()
dans la définition de la classe. Son fonctionnement est ressemblant à celui du constructeur, au lieu d’intervenir à la suite de la création, il intervient à la suite de la destruction de l’objet.
Malheureusement, l’interpréteur python embarqué dans l’ENIBook n’est pas complet. Il ne sait pas utiliser la fonction __del__
.
Il vous est donc demandé de tester et d’étudier l’exécution du programme suivant avec un vrai interpréteur python.
class A:
def __init__(self,name):
self.name=name
print("new ",self)
def __del__(self):
print("delete ",self)
def __repr__(self):
str = "A instance : "+ self.name
return str
print("---------start---------")
x=A('titi')
y=A('tutu')
z=A('toto')
#dereferencing 'toto'
print(">>> z = None")
z = None
#copy 'titi' reference into y
print(">>> y = x")
y = x
#x and y reference 'titi', 'tutu' has been forgotten
print(">>> x = None")
x = None
#no effect : 'titi' is still alive because of y
print("---------end---------")
#python destroy everything when the program ends
delete
en C++)Bien que les termes destructeur (destructor) et finalizer soient synonymes, on utilise plutôt finalizer dans le premier cas et destructeur dans le second. Si on fait abstraction du langage utilisé (par exemple en modélisation), c’est le terme destructeur qui est retenu.
En programmation orientée objet, la déclaration d’une classe regroupe des propriétés (attributs et méthodes) communes à un ensemble d’objets. La classe définit, d’une part, des attributs représentant l’état des objets et, d’autre part, des méthodes représentant leur comportement. Une classe représente donc une catégorie d’objets. Elle apparaît aussi comme un moule ou une usine à partir de laquelle il est possible de créer des objets ; c’est en quelque sorte une « boîte à outils » qui permet de fabriquer un objet. On parle alors d’un objet en tant qu’instance d’une classe (création d’un objet ayant les propriétés de la classe). (https://fr.wikipedia.org/wiki/Classe_(informatique))
son type, c’est à dire, la classe à laquelle il appartient
une identité qui le distingue des autres objets
Pour gérer l’initialisation des instances, une classe peut fournir un constructeur (ou initializer) : la fonction __init__()
. Cette fonction peut être paramétrée.
La destruction des objets déréférencés est assurée par le ramasse miette.
Pour gérer la destruction des instances, une classe peut fournir un destructeur appelé aussi finaliseur : la fonction __del__()
.
Toutes les fonctions membres sont définies avec un premier paramètre qui se nomme par convention self
et qui prendra pour valeur à l’exécution, une référence vers l’objet effectivement manipulé.
class MyClass:
```comments here the class purpose```
def __init__(self, parameter_1='default value'):
self.attribute_1=parameter_1
def __del__(self):
print("destruction de l'instance")
def doit(self, value):
if value < 42:
self.attribute_1=0.
a=MyClass('value')
a.doit(12)
a=None
Différencier les classes des objets (instances de classe) dans les propositions suivantes :
Footballeur, Mbappé, basket, sport, voiture, la Clio verte de ma grand mère , rouge, bleu, Homme, couleur, Guido van Rossum.
Clavier | Action |
---|---|
F1 | Afficher une aide technique |
F2 | Afficher une aide pédagogique |
Ctrl-A | Tout sélectionner |
Ctrl-C | Copier la sélection dans le presse-papier |
Ctrl-V | Copier le presse-papier dans la sélection |
Ctrl-X | Couper la sélection et la copier dans le presse-papier |
Ctrl-Z | Annuler la modification |
Maj-Ctrl-Z | Rétablir la modification |
Menu | Action |
---|---|
Ré-initialiser les sorties | |
Faire apparaître le menu d'aide | |
Valider la zone de saisie | |
Initialiser la zone de saisie | |
Charger le contenu d'un fichier dans la zone de saisie | |
Sauvegarder le contenu de la zone de saisie dans un fichier | |
Imprimer le contenu de la zone de saisie |
Les étudiants sont caractérisés par leur numéro d’étudiant, leur nom, leur prénom, leur date de naissance et leur adresse.
Identifier les attributs de la classe Etudiant
.
Clavier | Action |
---|---|
F1 | Afficher une aide technique |
F2 | Afficher une aide pédagogique |
Ctrl-A | Tout sélectionner |
Ctrl-C | Copier la sélection dans le presse-papier |
Ctrl-V | Copier le presse-papier dans la sélection |
Ctrl-X | Couper la sélection et la copier dans le presse-papier |
Ctrl-Z | Annuler la modification |
Maj-Ctrl-Z | Rétablir la modification |
Menu | Action |
---|---|
Ré-initialiser les sorties | |
Faire apparaître le menu d'aide | |
Valider la zone de saisie | |
Initialiser la zone de saisie | |
Charger le contenu d'un fichier dans la zone de saisie | |
Sauvegarder le contenu de la zone de saisie dans un fichier | |
Imprimer le contenu de la zone de saisie |
number
: un entier qui représente le numéro d’étudiantname
: une chaîne de caractère pour le nomfirstname
: une chaîne de caractère pour le prénombirthday
: une liste de 3 entiers pour la date de naissanceaddress
: une chaîne de caractères pour l’adresseUn complexe z est composé d’une partie réelle x
et d’une partie imaginaire y
, tel que z = x + i*y
.
Nous proposons ici d’implémenter une classe Complex.
1-Définir une classe Complex
.
2-Commenter la classe Complex
En python, si la première ligne suivant l’instruction class
est une chaîne de caractères, celle-ci sera considérée comme un commentaire et incorporée automatiquement dans un dispositif de documentation des classes qui fait partie intégrante de Python (docstring
). Prenez donc l’habitude de toujours placer une chaîne décrivant la classe à cet endroit.
x
et y
x
et y
à l’instanciationclass Complex:
"""allows complex number instanciation"""
def __init__(self, real=0., imaginary=0.):
self.r = real
self.i = imaginary
1-Instanciation :
Nous venons de définir une classe
Complex
. Nous pouvons dès à présent nous en servir pour créer des objets de ce type, par instanciation. Créer un objetc1
, instance de la classeComplex
.
2-Identité
- Effectuer les manipulations avec notre nouvel objet c1 suivantes :
>>> print(c1.__doc__)
>>> print(c1)
Que remarquez vous ? (cette question doit être traité en dehors de l’ENIBook pour faire la bonne observation).
3-Comportement
Affecter la valeur
3
à l’attributx
et4
à l’attributy
de l’objetc1
.
- Les fonctions peuvent utiliser des objets comme paramètres.
- Définir une fonction
show_complex
qui affiche les valeurs du complexe passé en paramètre.- Testez la avec l’objet
c1
.Créer une méthode (fonction dans la classe) show qui affiche les attributs de la classe
Complex
. Testez la avec l’objetc1
.
c2
de type Complex
, tester compare_complex
5-Manipulation de plusieurs objets Python
Affecter la valeur
3
à l’attributx
et4
à l’attributy
de l’objetc2
.Ajouter une méthode clone qui recopie un objet de type
Complex
.
- Effectuer les manipulations suivantes :
>>> print(c1 == c2)
>>> c1=c2; print(c1 == c2)
>>> c3=c1.clone(); print(c1 == c3)
Que remarquez vous ?
class Complex:
"""allows complex number instanciation"""
def __init__(self, real=0., imaginary=0.):
self.r = real
sefl.i = imaginary
n_real = Complex(12)
n_complex = Complex(1,1)
n_imaginary = Complex(imaginary=5)
1- Créer une classe Chiformi
pour représenter les noms des deux joueurs et leurs choix (P : pierre – F : feuille – C : ciseaux)
2- Créer un constructeur qui accepte les arguments suivants : nom_joueur_1
, choix_joueur_1
, nom_joueur_2
, choix_joueur_2
afficher_resultat
» qui permet d’afficher le résultat après le match. Le principe est le suivant :4- Créer une instance ch
pour tester la méthode afficher_resultat
1- Définir une classe
Arithmetique
pour représenter deux nombres A et B.2- Créer un constructeur paramétré qui accepte comme arguments les deux nombres A et B
3- Créer 4 méthodes
additionner
,soustraire
,multiplier
etdiviser
pour effectuer des opérations arithmétiques sur A et B4- Créer une méthode « afficher » pour afficher les résultats des 4 opérations
1- Définir une classe Python
CompteBancaire
qui représente un compte bancaire, ayant pour attributs :numero_compte
,nom
,prenom
etsolde
.2- Créer un constructeur qui accepte comme paramètres :
numero_compte
,nom
,prenom
,solde
.3- Créer une méthode
verser()
qui gère les versements.4- Créer une méthode
vetirer()
qui gère les retraits.5- Créer une méthode
appliquer_interets()
permettant d’appliquer les taux d’intérêt à un pourcentage de 5 % du solde6- Créer une méthode
afficher_details()
permettant d’afficher les détails sur le compte7- Instancier deux comptes bancaires de votre choix
8- Afficher les détails des deux comptes
Pourquoi le code suivant ne provoque pas la destruction de l’objet:
class Test:
def __init__(self):
self.my_self=self
def __del__(self):
print('destroy')
obj=Test()
del obj
print("fin du programme")
Tester ce code hors de l’ENIBook
En s’auto-référençant, l’objet neutralise le ramasse miettes.
titre : Le titre du livre, auteur : L’auteur du livre, prix : Le prix du livre, annee : L’année du livre.
Livre(), Livre(titre), Livre(titre, auteur), Livre(titre, auteur, prix), Livre(titre, auteur, prix, annee)
3- Instancier deux livres de votre choix
4- Créer une fonction qui renvoie vrai si le titre de l’instance passée en paramètre correspond au titre demandé.
à venir
1- Définir une nouvelle classe Time, qui nous permettra d’exécuter toute une série d’opérations sur des instants, des durées, etc.
2- Ajouter les attributs pays, heure, minute et seconde.
3- Créer un objet instant qui contient une date particulière.
4- Ecrire une fonction affiche heure() qui permet de visualiser le contenu d’un objet de classe Time sous la forme conventionnelle “heure :minute :seconde”.
5- Appliquer à l’objet instant la fonction affiche heure()
6- Encapsuler la fonction affiche heure() dans la classe Time (méthode affiche)
7- Instancier un objet maintenant
8- Tester la méthode
à venir
Pour obtenir les nombres premiers compris entre 2 et un certain entier N on va construire une liste chaînée d’objets appelés des MangeNombres, chacun comportant deux variables d’instance : un nombre premier et une référence sur le MangeNombres suivant de la liste. Le comportement d’un MangeNombres se réduit à l’opération “manger un nombre”. Le MangeNombres mp associé au nombre p mange les multiples de p : si on lui donne à manger un nombre q qui n’est pas multiple de p, mp le donne à manger à son MangeNombres suivant, s’il existe. Si mp n’a pas de suivant, celui-ci est crée, associé à q. La création d’un MangeNombres mp provoque l’affichage de p. Définissez la classe MangeNombres et écrivez un programme affichant les nombres premiers entre 2 et N.
à venir
Les éléments d’un ensemble seront mémorisés dans une variable d’instance de type tableau. Pour essayer la classe Ensemble, écrivez un programme déterminant les nombres différents contenus dans une suite de nombres lue sur l’entrée standard.
2- Modifiez la classe précédente afin de permettre la représentation d’ensembles d’entiers quelconques, c’est- à-dire non nécessairement compris entre 0 et une constante N connue à l’avance. Faites en sorte que l’interface de cette classe soit identique à celle de la version précédente qu’il n’y ait rien à modifier dans les programmes qui utilisent la première version de cette classe.
à venir