{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "< [Boucle for](PythonIntroCh6_FR.ipynb) | [Contenu](PythonIntro_FR.ipynb) | [Modules](PythonIntroCh8_FR.ipynb) >" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 7. Classes\n", "## 7.1 Introduction\n", "Une chose que vous apprendrez à connaître sur la programmation, est que les programmeurs aiment être paresseux. Si quelque chose a déjà été fait, pourquoi le refaire?\n", "\n", "C'est ce à quoi servent les fonctions en Python. Vous avez déjà fait faire quelque chose de spécial à votre code. Maintenant, vous voulez le refaire. Vous placez ce code spécial dans une fonction et vous le réutilisez pour ce qu'il vaut. Vous pouvez faire référence à une fonction n'importe où dans votre code, et l'ordinateur saura toujours de quoi vous parlez. Pratique, non?\n", "\n", "Bien sûr, les fonctions ont leurs limites. Les fonctions ne stockent pas d'informations comme le font les variables - chaque fois qu'une fonction est exécutée, elle repart à zéro. Cependant, certaines fonctions et variables sont étroitement liées les unes aux autres et doivent souvent interagir entre elles. Par exemple, imaginez que vous ayez un club de golf. Il contient des informations (c'est-à-dire des variables) comme la longueur du manche, le matériau de la poignée et le matériau de la tête. Des fonctions lui sont également associées, comme celle de \"swinguer\" votre club de golf ou celle de le casser par pure frustration. Pour ces fonctions, vous devez connaître les variables de la longueur du manche, du matériau de la tête, etc.\n", "\n", "Cela peut être facilement géré avec des fonctions normales. Les paramètres affectent l'effet d'une fonction. Mais qu'en est-il si une fonction doit affecter des variables ? Que se passe-t-il si, à chaque fois que vous utilisez votre club de golf, le manche s'affaiblit, la poignée s'use un peu, vous êtes un peu plus frustré et une nouvelle rayure se forme sur la tête du club ? Une fonction ne peut pas faire cela. Une fonction ne produit qu'une seule sortie, pas quatre, cinq ou cinq cents. Ce qu'il faut, c'est un moyen de regrouper les fonctions et les variables qui sont étroitement liées en un seul endroit afin qu'elles puissent interagir entre elles.\n", "\n", "Il y a de fortes chances que vous possédiez également plus d'un club de golf. Sans classes, vous devez écrire tout un tas de code pour chaque club de golf différent. C'est un problème, car tous les clubs ont des caractéristiques communes, mais certains ont des propriétés différentes, comme la composition du manche et son poids. L'idéal serait d'avoir un design de votre club de golf de base. Chaque fois que vous créez un nouveau club, il vous suffit de spécifier ses attributs - la longueur de son manche, son poids, etc.\n", "\n", "Et si vous voulez un club de golf doté de caractéristiques supplémentaires ? Peut-être décidez-vous de fixer une horloge à votre club de golf (pourquoi, je ne sais pas - c'était votre idée). Cela signifie-t-il que nous devons créer ce club de golf à partir de zéro ? Devrions-noud d'abord écrire le code de notre club de golf de base, tout cela à nouveau, ainsi que le code de l'horloge, pour notre nouveau design. Ne serait-il pas préférable de prendre notre club de golf existant et d'y ajouter le code de l'horloge?\n", "\n", "Ce sont des problèmes qu'une chose appelée programmation orientée objet résout. Elle met les fonctions et les variables ensemble de manière à ce qu'elles puissent se voir et travailler ensemble, être répliquées et modifiées selon les besoins, et non pas quand c'est inutile. Et nous utilisons une chose appelée `class` pour faire cela.\n", "\n", "## 7.2 Création d'une `Class`\n", "Qu'est-ce qu'une class ? Pensez à une class comme à un plan (blueprint). Elle n'est pas quelque chose en soi, elle décrit simplement comment faire quelque chose. Vous pouvez créer un grand nombre d'objets à partir de ce plan - ce que l'on appelle techniquement une *instance*.\n", "\n", "Comment créer ces soi-disant 'classes'? Très facilement, avec l'opérateur `class`:\n", "\n", "```Python\n", "# Définition d'une class\n", "class nom_class:\n", " [instruction 1]\n", " [instruction 2]\n", " [instruction 3]\n", " [etc]\n", "```\n", "\n", "Cela n'a pas beaucoup de sens ? Ce n'est pas grave, voici un exemple, qui crée la définition d'une `Forme`(Shape):\n", "\n", "```Python\n", "#Un exemple de class\n", "class Forme:\n", " def __init__(self,x,y):\n", " self.x = x\n", " self.y = y\n", " description = \"Cette forme n'a pas encore été décrite\"\n", " auteur = \"Personne n'a encore prétendu réaliser cette forme\"\n", " def aire(self):\n", " return self.x * self.y\n", " def périmètre(self):\n", " return 2 * self.x + 2 * self.y\n", " def décrire(self,text):\n", " self.description = text\n", " def nom_Auteur(self,text):\n", " self.auteur = text\n", " def échelletaille(self,scale):\n", " self.x = self.x * scale\n", " self.y = self.y * scale\n", "```\n", "\n", "Ce que vous avez créé est une description d'une forme (c'est-à-dire les variables) et les opérations que vous pouvez faire avec cette forme (c'est-à-dire les fonctions). Ceci est très important - vous n'avez pas créé une forme réelle, simplement la description de ce qu'est une forme. La forme a une largeur (`x`), une hauteur (`y`), une aire et un périmètre (`aire(self)` et `périmètre(self)`). Aucun code n'est exécuté lorsque vous définissez une classe - vous créez simplement des fonctions et des variables.\n", "\n", "La fonction appelée `__init__` est exécutée lorsque nous créons une instance de `Forme` - c'est-à-dire, lorsque nous créons une forme réelle, par opposition au 'plan ou blueprint' que nous avons ici, `__init__` est exécutée. Vous comprendrez plus tard comment cela fonctionne.\n", "\n", "`self` est la façon dont nous nous référons aux choses dans la class à partir d'elle-même. `self` est le premier paramètre de toute fonction définie à l'intérieur d'une class. Toute fonction ou variable créée au premier niveau d'indentation (c'est-à-dire les lignes de code qui commencent une TAB à droite de l'endroit où nous avons placé la classe `Forme`) est automatiquement placée dans self. Pour accéder à ces fonctions et variables ailleurs dans la class, leur nom doit être précédé de `self` et d'un point (par exemple `self.nom_variable`). Sans `self`, vous ne pouvez utiliser les variables qu'à l'intérieur de la fonction où elles sont définies, pas dans d'autres fonctions de la même `class`.\n", "\n", "## 7.3 Utilisation d'une `class`\n", "C'est bien beau de pouvoir créer une classe, mais comment l'utiliser ? Voici un exemple de ce que nous appelons la création d'une instance d'une class. Supposons que le code ci-dessus ait déjà été exécuté:\n", "\n", "```Python\n", "rectangle = Forme(100,45)\n", "```\n", "\n", "Qu'est-ce qui a été fait ? Cela demande un peu d'explications...\n", "\n", "C'est maintenant que la fonction `__init__` entre vraiment en jeu. On crée une instance d'une class en lui donnant d'abord son nom (dans ce cas, `Forme`) puis, entre parenthèses, les valeurs à passer à la fonction `__init__`. La fonction init s'exécute (en utilisant les paramètres que vous lui avez donnés entre parenthèses) et génère une instance de cette class, qui dans ce cas porte le nom de `rectangle`.\n", "\n", "Pensez à notre instance de class, `rectangle`, comme une collection autonome de variables et de fonctions. De la même manière que nous avons utilisé `self` pour accéder aux fonctions et aux variables de l'instance de class depuis l'intérieur de celle-ci, nous utilisons le nom que nous lui avons attribué (rectangle) pour accéder aux fonctions et aux variables de l'instance de classe depuis l'extérieur de celle-ci. En ajoutant tout le code ci-dessus, nous ferions ceci :" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class Forme:\n", " def __init__(self,x,y):\n", " self.x = x\n", " self.y = y\n", " description = \"Cette forme n'a pas encore été décrite\"\n", " auteur = \"Personne n'a encore prétendu réaliser cette forme\"\n", " def aire(self):\n", " return self.x * self.y\n", " def périmètre(self):\n", " return 2 * self.x + 2 * self.y\n", " def décrire(self,text):\n", " self.description = text\n", " def nom_Auteur(self,text):\n", " self.auteur = text\n", " def échelletaille(self,scale):\n", " self.x = self.x * scale\n", " self.y = self.y * scale\n", " \n", "rectangle = Forme(100,45)\n", "\n", "#trouver l'aire de votre rectangle:\n", "print(rectangle.aire())\n", "\n", "#trouver le périmètre de votre rectangle:\n", "print(rectangle.périmètre())\n", "\n", "#décrire le rectangle\n", "rectangle.décrire(\"Un large rectangle, avec une longueur plus\\\n", " de fois sa largeur\")\n", "\n", "#réduction de 50 % de la taille du rectangle\n", "rectangle.échelletaille(0.5)\n", "\n", "#réaffichage de la nouvelle surface du rectangle\n", "print(rectangle.aire())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Comme vous le voyez, là où `self` serait utilisé à l'intérieur de l'instance de la class, son nom assigné est utilisé à l'extérieur de la class. Nous faisons cela pour voir et modifier les variables à l'intérieur de la class, et pour accéder aux fonctions qui s'y trouvent.\n", "\n", "Nous ne sommes pas limités à une seule instance d'une class - nous pouvons avoir autant d'instances que nous le souhaitons. Je pourrais faire ceci:\n", "```Python\n", "longrectangle = Forme(120,10)\n", "largerectangle = Forme(130,120)\n", "```\n", "\n", "et `longrectangle` et `largerectangle` ont tous deux leurs propres fonctions et variables - ils sont totalement indépendants les uns des autres. Il n'y a pas de limite au nombre d'instances que je peux créer.\n", "\n", "Essayez avec quelques instances différentes dans le champ ci-dessus." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 7.4 Termes\n", "La programmation orientée objet est associée à un certain nombre de termes (lingo). Il est temps de mettre tout cela au clair:\n", "* Lorsque nous décrivons pour la première fois une `class`, nous la *définissons* (comme pour les fonctions)\n", "* La possibilité de regrouper des fonctions et des variables similaires est appelée *encapsulation*\n", "* Le mot `class` peut être utilisé pour décrire le code dans lequel la classe est définie (comme pour la définition d'une fonction), et il peut aussi faire référence à une instance de cette `class` - cela peut prêter à confusion, alors assurez-vous de savoir sous quelle forme nous parlons des classes.\n", "* Une variable dans une classe est appelée un *Attribut* (Attribute)\n", "* Une fonction dans une classe est appelée une *méthode* (method)\n", "* Une class fait partie de la même catégorie de choses que les variables, les listes, les dictionnaires, etc. C'est-à-dire que ce sont des *objets*\n", "* Une class est connue comme une \"structure de données\" (data structure)- elle contient des données et les méthodes pour traiter ces données." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 7.5 Héritage\n", "Revenons sur l'introduction. Nous savons que les class regroupent des variables et des fonctions, appelées attributs et méthodes, de sorte que les données et le code pour les traiter se trouvent au même endroit. Nous pouvons créer un nombre quelconque d'instances de cette classe, de sorte que nous n'avons pas à écrire un nouveau code pour chaque nouvel objet que nous créons. Mais qu'en est-il de l'ajout de fonctionnalités supplémentaires à la conception de notre club de golf ? C'est là que l'*héritage* (inheritance) entre en jeu.\n", "\n", "Python rend l'héritage très facile. Nous définissons une nouvelle class, basée sur une autre class 'parent'. Notre nouvelle classe reprend tout ce qui se trouve dans la class parent, et nous pouvons également y ajouter d'autres éléments. Si un nouvel attribut ou une nouvelle méthode porte le même nom qu'un attribut ou une méthode de notre class parent, il est utilisé à la place de la class parent. Vous vous souvenez de la classe `Forme`?\n", "\n", "```Python\n", "class Forme:\n", " def __init__(self,x,y):\n", " self.x = x\n", " self.y = y\n", " description = \"Cette forme n'a pas encore été décrite\"\n", " auteur = \"Personne n'a encore prétendu réaliser cette forme\"\n", " def aire(self):\n", " return self.x * self.y\n", " def périmètre(self):\n", " return 2 * self.x + 2 * self.y\n", " def décrire(self,text):\n", " self.description = text\n", " def nom_Auteur(self,text):\n", " self.auteur = text\n", " def échelletaille(self,scale):\n", " self.x = self.x * scale\n", " self.y = self.y * scale\n", "```\n", "\n", "Si nous voulions définir une nouvelle classe, disons un carré, basée sur notre class Forme précédente, nous ferions ceci :\n", "\n", "```Python\n", "class Carré(Forme):\n", " def __init__(self,x):\n", " self.x = x\n", "\t self.y = x\n", "```\n", "\n", "C'est exactement comme la définition normale d'une class, mais cette fois, nous mettons entre parenthèses après le nom, la class parent dont nous avons hérité. Comme vous le voyez, nous avons décrit un carré très *rapidement* grâce à cela. C'est parce que nous avons hérité de la classe forme et que nous n'avons modifié que ce qui devait l'être. Dans ce cas, nous avons redéfini la fonction `__init__` de Forme pour que les valeurs de X et Y soient les mêmes.
\n", "\n", "Partons de ce que nous avons appris, et créons une autre nouvelle classe, cette fois héritée de `Carré`. Il s'agira de deux carrés, l'un immédiatement à gauche de l'autre:\n", "```Python\n", "# La forme ressemble à ceci:\n", "# _________\n", "#| | |\n", "#| | |\n", "#|____|____|\n", "\n", "class DoubleCarré(Carré):\n", " def __init__(self,y):\n", " self.x = 2 * y\n", " self.y = y\n", " def périmètre(self):\n", " return 2 * self.x + 3 * self.y\n", "```\n", "\n", "Cette fois, nous avons également dû redéfinir la fonction `périmètre`, car il y a une ligne qui passe au milieu de la forme. Essayez de créer une instance de cette class dans le champ ci-dessous et jouez avec différentes valeurs. Puisque la `class Forme` a déjà été exécutée, vous pouvez simplement ajouter seulement les nouvelles classes ici et ajouter définir les instances." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class Carré(Forme):\n", " def __init__(self,x):\n", " self.x = x\n", " self.y = x\n", " \n", "# La forme ressemble à ceci:\n", "# _________\n", "#| | |\n", "#| | |\n", "#|____|____|\n", "\n", "class DoubleCarré(Carré):\n", " def __init__(self,y):\n", " self.x = 2 * y\n", " self.y = y\n", " def périmètre(self):\n", " return 2 * self.x + 3 * self.y\n", "testcarré = Carré(5)\n", "testdouble = DoubleCarré(6)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 7.6 Pointeurs et dictionnaires de class\n", "En y repensant, lorsque vous dites qu'une variable est égale à une autre, par exemple `variable2 = variable1`, la variable à gauche du signe égal prend la valeur de la variable à droite. Avec les instances de classe, cela se passe un peu différemment - le nom à gauche devient l'instance de class à droite. Ainsi, dans `instance2 = instance1`, `instance2` \"pointe\" vers `instance1` - il y a deux noms donnés à l'instance de class, et vous pouvez accéder à l'instance de class par l'un ou l'autre nom. \n", "\n", "Dans d'autres langages, vous faites ce genre de choses en utilisant des *pointeurs*, mais en Python, tout cela se passe en coulisses.\n", "La dernière chose que nous allons aborder est les dictionnaires de class. En gardant à l'esprit ce que nous venons d'apprendre sur les pointeurs, nous pouvons affecter une instance d'une class à une entrée dans une liste ou un dictionnaire. Cela permet à n'importe quel nombre d'instances de class d'exister lorsque notre programme est exécuté. Regardons l'exemple ci-dessous et voyons comment il décrit ce dont je parle:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Une fois de plus, supposons que les définitions de Forme,\n", "# Carré et DoubleCarré ont été exécutées.\n", "# Tout d'abord, créez un dictionnaire (dictionary):\n", "dictionnaire = {}\n", "\n", "# Ensuite, créez quelques instances de class dans le dictionnaire:\n", "dictionnaire[\"DoubleCarré 1\"] = DoubleCarré(5)\n", "dictionnaire[\"long rectangle\"] = Forme(600,45)\n", "\n", "#Vous pouvez maintenant les utiliser comme une class normale :\n", "print(dictionnaire[\"long rectangle\"].aire())\n", "\n", "dictionnaire[\"DoubleCarré 1\"].nom_Auteur(\"The Gingerbread Man\")\n", "print(dictionnaire[\"DoubleCarré 1\"].auteur)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Comme vous le voyez, nous avons simplement remplacé notre vieux nom ennuyeux sur le côté gauche par une entrée de dictionnaire passionnante, nouvelle et dynamique. Plutôt cool, non?\n", "## 7.7 Conclusion\n", "Et voilà la leçon sur les class ! Vous n'imaginez pas le temps qu'il m'a fallu pour rédiger ce texte de façon claire et nette, et je ne suis toujours pas complètement satisfait ! J'ai déjà parcouru et réécrit la moitié de cette leçon une fois, et si vous êtes toujours confus, je vais probablement la parcourir à nouveau. J'ai probablement embrouillé certains d'entre vous avec ma propre confusion sur ce sujet, mais rappelez-vous - ce n'est pas le nom d'une chose qui est important, mais ce qu'elle fait (cela ne fonctionne pas dans un contexte social, croyez-moi... ;))." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "< [Boucle for](PythonIntroCh6_FR.ipynb) | [Contenu](PythonIntro_FR.ipynb) | [Modules](PythonIntroCh8_FR.ipynb) >" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.5" } }, "nbformat": 4, "nbformat_minor": 2 }