Ceci est une ancienne révision du document !
Warning: Undefined array key "pos" in /home/goupillf/wiki.goupill.fr/lib/plugins/mdpage/src/DokuWiki/Plugin/Mdpage/MarkdownRendererTrait.php on line 100
Warning: Undefined array key "pos" in /home/goupillf/wiki.goupill.fr/lib/plugins/mdpage/src/DokuWiki/Plugin/Mdpage/MarkdownRendererTrait.php on line 100
Warning: Undefined array key "pos" in /home/goupillf/wiki.goupill.fr/lib/plugins/mdpage/src/DokuWiki/Plugin/Mdpage/MarkdownRendererTrait.php on line 100
Warning: Undefined array key "pos" in /home/goupillf/wiki.goupill.fr/lib/plugins/mdpage/src/DokuWiki/Plugin/Mdpage/MarkdownRendererTrait.php on line 100
Warning: Undefined array key "pos" in /home/goupillf/wiki.goupill.fr/lib/plugins/mdpage/src/DokuWiki/Plugin/Mdpage/MarkdownRendererTrait.php on line 100
Warning: Undefined array key "pos" in /home/goupillf/wiki.goupill.fr/lib/plugins/mdpage/src/DokuWiki/Plugin/Mdpage/MarkdownRendererTrait.php on line 100
Warning: Undefined array key "pos" in /home/goupillf/wiki.goupill.fr/lib/plugins/mdpage/src/DokuWiki/Plugin/Mdpage/MarkdownRendererTrait.php on line 100
Warning: Undefined array key "pos" in /home/goupillf/wiki.goupill.fr/lib/plugins/mdpage/src/DokuWiki/Plugin/Mdpage/MarkdownRendererTrait.php on line 100
Warning: Undefined array key "pos" in /home/goupillf/wiki.goupill.fr/lib/plugins/mdpage/src/DokuWiki/Plugin/Mdpage/MarkdownRendererTrait.php on line 100
Warning: Undefined array key "pos" in /home/goupillf/wiki.goupill.fr/lib/plugins/mdpage/src/DokuWiki/Plugin/Mdpage/MarkdownRendererTrait.php on line 100
Warning: Undefined array key "pos" in /home/goupillf/wiki.goupill.fr/lib/plugins/mdpage/src/DokuWiki/Plugin/Mdpage/MarkdownRendererTrait.php on line 100
Warning: Undefined array key "pos" in /home/goupillf/wiki.goupill.fr/lib/plugins/mdpage/src/DokuWiki/Plugin/Mdpage/MarkdownRendererTrait.php on line 100
Warning: Undefined array key "pos" in /home/goupillf/wiki.goupill.fr/lib/plugins/mdpage/src/DokuWiki/Plugin/Mdpage/MarkdownRendererTrait.php on line 100
Warning: Undefined array key "pos" in /home/goupillf/wiki.goupill.fr/lib/plugins/mdpage/src/DokuWiki/Plugin/Mdpage/MarkdownRendererTrait.php on line 100
Warning: Undefined array key "pos" in /home/goupillf/wiki.goupill.fr/lib/plugins/mdpage/src/DokuWiki/Plugin/Mdpage/MarkdownRendererTrait.php on line 100
Warning: Undefined array key "pos" in /home/goupillf/wiki.goupill.fr/lib/plugins/mdpage/src/DokuWiki/Plugin/Mdpage/MarkdownRendererTrait.php on line 100
Table des matières
Classes - Programmation orientée objet (POO)
Apparu en 1960 avec LISP. Très répandu aujourd'hui. En Java, presque tout est objet.
Paradigme de programmation
Programmation procédurale
Le programme est découpé en fonctions ou procédures – fonctions qui ne retournent rien – réalisant chacune un morceau de l'ensemble.
Les fonctions sont alors séparées des données traitées, elles sont toutes au même niveau, et les données subissent passivement.
Programmation orientée objet
Les données sont structurées en objets qui définissent des méthodes – fonction d'un objet – pouvant leur être appliquées, et des attributs.
Les objets sont responsables de l'exécution des fonctions, ils ne sont plus passifs. Les fonctions sont organisées selon les objets auxquelles elles appartiennent.
Dans l'exercice on a vu que dans un projets grandissant, on avait des problèmes d'organisation :
- beaucoup de fonctions, il faut allonger leurs noms pour bien les distinguer,
- pas facile de contrôler les données sur lesquelles agissent les fonctions.
Pour le dire simplement, la POO permet d'organiser mieux le programme ce qui permet de faciliter le développement de gros programmes.
Notion de classe
Un objet est défini par une classe. La classe est la notice qui détaille comme on fabrique l'objet et comment on l'utilise.
- La classe définit des méthodes (fonctions dans une classe) qui définissent le fonctionnement de la classe et son interface – comment l'extérieur peut agir sur un objet.
- Elle a aussi des attributs, c'est à dire des données qui la caractérisent. Par exemple, objet Humain aurait les attributs genre, age…
Cela rappelle la définition abstraite des structures de données – Listes, Piles et Files – consistant à préciser toutes les propriétés désirées. Une classe est une implémentation qui énonce naturellement les propriétés désirées.
## Définir une classe
Dans l'exercice on rencontre les problèmes :
* Pas de façon simple pour regrouper toutes les données concernant le triangle, * pas de façon simple de savoir qu'une variable contient un triangle, et un triangle bien formé, * multiplication de fonctions en vrac.
Les classes vont permettre de simplifier ces problèmes :
* La classe définit un modèle qui définit la façon de créer et d'utiliser des objets. Une classe `Triangle` permettra de définir exactement les données contenues dans un objet de ce type.
* Dès lors qu'un objet est de type `Triangle`, on est certain qu'il respecte les contraintes qu'on a fixé dans la classe.
* Chaque classe définit ses propres fonctions de sorte que le code est mieux organisé.
### Comment procéder
*Remarque :* par convention, le nom d'une classe utilise *CamelCase* : première lettre en majuscule, majuscule à chaque changement de mot : `Triangle`, `TriangleIsocele`…
On peut suivre les étapes suivantes : * Identifier les objets et leurs attributs : quelles sont les données caractérisant les objets * Identifier les opérations : comment utilise-t-on l'objet, notamment depuis l’extérieur ? * Définir la visibilité : quels attributs / méthodes sont visibles par les autres ? * Implémentation
*On reviendra plus tard sur la notion de visibilité.*
### Exercice 2
Reprenons le cas du triangle.
* Quels sont ses attributs ? * Quelles sont les opérations possibles ?
Pour représenter ces choix de façons claire et explicite, on utilise un diagramme UML - *Unified Modeling Language*
| Triangle |
| :————- |
| Attributs |
| + réel : x1 |
| + réel : y1 |
| + réel : x2 |
| + réel : y2 |
| + rée l: x3 |
| + réel : y3 |
| + texte : nom |
| Méthodes |
| + construire(réels: x1, y1, x2, y2, x3, y3, texte:nom) |
| + réel : aire() |
| + réel : périmètre() |
Dans un programme complexe, le diagramme UML permettra d'indiquer les dépendances entre les différentes classes.
## Classe en Python
Voici un modèle </markdown>
- triangle.py
class Triangle: # on utilise PascalCase = UpperCamelCase """ Commentaires utiles pour la lisibilité et la documentation """ x1: float # ceci n'est pas requis en Python y1: float # mais peut rendre le code plus clair x2: float y2: float x3: float y3: float nom: string def __init__(self, x1, y1, x2, y2, x3, y3, nom): """ Constructeur - Gère l'initialisation de l'objet """ self.x1 = x1 # x1 est la valeur fournie # self.x1 est l'attribut x1 de l'objet self.y1 = y1 # etc. def aire(self) -> float : """ Documentation... -> float n'est pas requis en Python. Il permet seulement de préciser ce qui est attendu en sortie. """ # formule de maths... return abs((self.x2 - self.x1)*(self.y3 - self.y1) - (self.y2 - self.y1)*self(x3 - self.x1))/2 # puis les autres méthodes...
Exemple d'utilisation
monTriangle = Triangle(0,0,5,2,3,4,'T1') monTriangle.aire() # retourne 7.0 type(monTriangle) # retourne Triangle, ce qui s'affiche <class : Triangle>
Instanciation
Parallelogramme et Triangle sont des modèles. Quand on définit la classe Triangle, on ne crée aucun objet de type Triangle. On se contente de dire comment faire pour en créer un ou pour l'utiliser.
Quand on écrit monTriangle = Triangle(...), on crée un objet de type Triangle.
On dit que monTriangle est une instance de la classe Triangle.
Autrement dit : On peut définir ce qu'est une voiture et comment on fait une voiture dans un livre. Ce livre n'en est pas pour autant une voiture. Quand on suit le livre et qu'on fabrique la voiture, on obtient une réalisation de voiture, c'est une instance de voiture.
Une classe peut avoir plusieurs instances : on peut créer plusieurs objets Triangle en suivant le guide que constitue la classe Triangle.
Autrement dit : On peut fabriquer plusieurs voitures, identiques ou pas.
Exercice 3
- Dans un fichier
triangle.pydéfinissez la classeTriangle- complétez ce qui manque dans le modèle donné plus haut. - En fin de fichier, ajoutez le code de test suivant et exécutez :
if __name__ == '__main__': monTriangle = Triangle(0,0,5,2,3,4,'T1') a = monTriangle.aire() p = monTriangle.perimetre() print("L'aire du triangle est de {}".format(a)) print("Le périmètre du triangle est de {}".format(p))
Très important :
L'instanciation
monTriangle = Triangle(...)provoque l'exécution du constructeur.En Python, il s'agit de la méthode
__init__(...)En tant qu'objet de type
Triangle,monTrianglea des attributs et des méthodes.monTriangle.x1désigne l'attributx1̀ deTriangle`monTriangle.airedésigne la méthodeairedemonTriangle.
Dans la signature et le corps de la méthode
aire, on peut voire apparaître le mot-cléself. La méthodeairene peut être appelée que via une instance, par exemplemonTriangle.aire().selfdésigne alors l'instance elle-même.
tA.aire() # dans ce cas, self = tA tB.aire() # dans ce cas, self = tB
D'autres langages préfèrent utiliser le mot-clé this.
En Python, lors de la définition de la méthode
aire, on est obligé de préciser l'argumentselfen première position.Au moment de l'appel, on ne précise pas
self. On se contente de par extA.aire(), etselfest automatiquement mis égal àtA. Tout se passe comme si on avait appeléTriangle.aire(tA).
L'objet est reponsable de l'action
Sans classe, on faisant aireTriangle(triangle) où triangle subissait la fonction passivement.
Maintenant, triangle est un objet doté de méthodes et on écrit triangle.aire(). L'objet est responsable du calcul.
Chaque type d'objet définit ses propres méthodes. Le code devient plus clair : triangle.aire(), rectangle.aire(). C'est léger, lisible, bien organisé.
Exercice 4
On peut considérer que le diagramme UML de la classe Parallelogramme est identique à ceci près qu'il faut ajouter un quatrième point.
- Dans un fichier
parallelogramme.pycréez une classeParallelogramme - En fin de fichier, ajoutez le code de test suivant et exécutez :
if __name__ == '__main__': monPlg = Parallelogramme(0,0,5,2,8,6,3,4,'P1') a = monPlg.aire() p = monPlg.perimetre() print("L'aire du plg. est de {}".format(a)) print("Le périmètre du plg. est de {}".format(p))
On peut voir ici que le code est presque identique à celui du cas Triangle. Quand on appelle la méthode aire(), aucune confusion n'est possible. monPlg est un objet Parallelogramme, donc monPlg.aire() appelle la méthode aire de Parallelogramme.
Que faudrait-il faire pour être certain qu'un objet
Paralllelogrammesoit toujours valide ?Comprendre : comment vérifier qu'il est valide au moment où on le crée et vérifier qu'il reste valide dans le cas où on modifie ses attributs.
Public / privé
plg = Parallelogramme(0,0,1,0,1,1,1,0,'P') plg.x1 = 10
Lors de la création plg est bien valide. Mais on modifie plg.x1 de sorte qu'il ne l'est plus...
Une solution est d'empêcher la modification de l'attribut x1 (et des autres) en le rendant privé.
Un attribut privé ne peut-être lu et modifié que depuis l'intérieur de la définition de la classe.
Remarque : En Python, il n'y a pas vraiment de gestion de l'accès public / privé. On convient de nommer les attributs / méthodes privé(e)s en les faisant précéder par _.
Dans l'exemple, ce serait donc _x1.
Dans le diagramme UML, on mettra - au lieu de +.
Remarque : Certains invitent à utiliser __ (deux underscore au lieu d'un). Ne vous y fiez pas trop. Ce choix crée des difficultés et ne constitue pas une protection.
La notion public / privé n'est pas explicite dans le programme. Ne vous en souciez donc pas trop, on pourra travailler en ne considérant que rien n'est privé.
Classes et sous-classes
- Éviter tout bloc de code trop compliqué : découper au maximum en fonctions.
- Idem pour les classes : Utiliser une classe spécifique pour tout type d'item.
Exercice 5
- Dans un fichier
point.py, définir une classePointayant les attributsx,y. - Ajouter la méthode
distance(self, autrePoint)de sorte que pour lesPointAetB, l'expressionA.distance(B)donne la distanceAB. - Modifier les classes
TriangleetParallelogrammepour qu'elles utilisent la classePointau lieu des paires de coordonnéesx, y.
Important
>>> p1 = Point(2,3) >>> p2 = Point(2,3) >>> p1 == p2 # la réponse est False !
Les deux points ont les mêmes coordonnées mais ce sont néanmoins deux points différents.
En écrivant Point(2,3) on crée un instance en mémoire. Quand on réécrit Point(2,3) on crée une nouvelle instance en mémoire même si elle a les mêmes attributs. Ce sont donc deux instances différentes.
Exercice 6 - problème de dépendance
Supposons que l'on veuille ajouter une fonction qui détermine si un point est contenu dans un triangle. Selon vous, quel est le meilleur choix ?
- dans la classe
Point, définir la méthodeestDansTriangle(self, triangle) - dans la classe
Triangle, définir la méthodecontientPoint(self, point) - en dehors des deux classes, définir une fonction
triangleContientPoint(triangle, point)
Dépendance en UML
Le diagramme UML permet de faire apparaître que la classe Triangle utilise la classe Point.
Méthodes magiques
Quand on définit x = 5 et y = 12, on peut faire avec x et y un certains nombres d'opérations qui seront comprises automatiquement par le langage.
- On peut additionner
x + y - On peut comparer
x <= y - On peut afficher
print(x), ce qui provoque le transtypage dex,int -> str
Si on souhaite pouvoir faire ce genre d'opérations avec notre objet, c'est possible !
Le cas le plus courant est l'affichage. On aimerait que le transtypage str(monPoint) produise une chaîne de caractère comme (15 ; 45) pour le point de coordonnées x = 15 et y = 45.
Il faut utiliser la méthode __str__(self)
# Dans la classe Point def __str__(self): """ transtypage -> str """ return "({} ; {})".format(self.nom, self.x, self.y)
Exercice 7
Ajoutez une fonction de transtypage vers str dans la classe Triangle.
Pensez à tester.
Exercice 8
- Proposez une implémentation pour les piles en utilisant des classes.
- Même chose pour les files.
- Dans les deux cas, prévoyez une méthode
__str__
Exercice 9 ++
On peut aller plus loin en ajoutant des vecteurs, en définissant le produit scalaire...
