{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Définition d'une classe\n",
    "\n",
    "Un objet est un élément qui contient des données et des fonctions.\n",
    "En python, tous les éléments qui sont mis à l'intérieur d'une variable sont des objets.\n",
    "\n",
    "Les données présentes dans un objet sont placées à l'interieur de variables que l'on appelle **attributs**.\n",
    "Les fonctions présentes dans l'objet sont appellées **fonctions membres**.\n",
    "\n",
    "La nature des données et des fonctions présentent dans un objet dépends de son type.\n",
    "\n",
    "Par exemple, l'entier 5 est un objet. Son type est 'Intger' et il contient beaucoups d'attributs et de fonctions membres.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "a = 5"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "type(a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "a.N()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Il existe des fonctions et des attributs **privés** qui n'apparaissent pas à l'utilisateur.\n",
    "Leurs noms commencent tous par \\_ .\n",
    "\n",
    "Il y a deux types d'attributs/functions :\n",
    " - les attributs/fonctions qui commencent par \\_ et finissent par \\_\n",
    " - les attributs/fonctions qui commencent par \\_ uniquement\n",
    " - les attributs/fonctions qui commencent par \\_\\_ et finissent par \\_\\_"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "a.__abs__"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "a._ascii_art_"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "a._add_parent"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Tous ces attributs sont des attributs qui sont cachés à l'utilisateur. \n",
    "Cela veut dire que l'utilisateur ne doit \"généralement\" pas les utiliser, ces fonctions sont utilisées par les développeurs de la\n",
    "classe et elles doivent être utilisée dans des conditions bien particulères."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "les attributs/fonctions qui commencnent par \\_ et finissent par \\_ sont des fonctions utilisés par le système sage pour\n",
    "manipuler l'objet.\n",
    "\n",
    "Par exemple '_latex_' est une fonction qui renvoie le code latex de l'objet. Si cette fonction existe dans un objet, \n",
    "alors Sage sait comment convertir l'objet en latex. Il utilisera cette fonction.\n",
    "\n",
    "Les attributs qui commencent par \\_ uniquement sont des fonctions privées crées par le developpeur pour le developpeur.\n",
    "\n",
    "Les attributs/fonctions qui commencent par \\_\\_ et finissent par \\_\\_ sont des fonctions utilisées par Python pour manipuler les objets.\n",
    "\n",
    "Par exemple, '__str__' est une fonction qui renvoie la chaîne de caractères assciée à un objet. Elle est utilisée par\n",
    "python pour afficher un objet sur le terminal."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "M = Matrix([[1,2,3],[4,5,6]])\n",
    "M._latex_()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "latex(M)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "str(M)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "print(M)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Il est possible de définir son propre type pour créer ses propres objets.\n",
    "\n",
    "Pour cela on créer une classe. Un classe contient toutes les informations\n",
    "qui caractérisent l'objet.\n",
    "Le nom de la classe est aussi le type de l'objet qui sera crée avec cette classe.\n",
    "\n",
    "Un classe c'est comme le plan d'une voiture :\n",
    " - Avec un plan, on créé autant de voitures que l'on souhaite\n",
    " - Avec une classe, on peut créer autant d'objets que l'on souhaite\n",
    " \n",
    " Voici le code minimal pour créer une classe."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "class Essai:\n",
    "    pass"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Nous venons de créer une classe Essai et donc un nouveau type d'objet Essai.\n",
    "\n",
    "Pour créer un objet de type Essai, il suffit d'écrire:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "e1 = Essai()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "e1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "On dit que e1 est une **instance** d'Essai.\n",
    "\n",
    "Le code 0x19ed61b48 est l'adresse où se trouve l'objet dans la mémoire. C'est un identifiant unique.\n",
    "En effet, on peut créer autant dobjets différents avec le même type."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "e2 = Essai(); e2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Les varaibles e1 et e2 contienent des objets différents.\n",
    "En effet, le premier est situé dans la mémoire à l'adresse : 0x19ed61b48 et le deuxième à l'addresse : 0x19ec9ed88.\n",
    "\n",
    "Pour savoir si deux objets sont les mêmes, vous pouvez utiliser l'opérateur is :"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "e1 is e2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Attention ! Deux objets peuvent être égaux, mais être représentes par deux objets distincts en mémoires."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "l1 = [1,2]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "l2 = [1,2]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "l1 is l2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "l1 == l2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cerrtains objets sont uniques en python, cela veut dire qu'ils sont réprésentées de manière uniques dans la mémoire.\n",
    "C'est le cas des chaînes de caractères."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "l1 = 'cou'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "l2 = 'cou'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "l1 is l2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "l1 == l2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Les données présentes dans un objet peuvent évoluer dans le temps.\n",
    "On peut par exemple, ajouter des attributs pendant la vie d'un objet :"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "e1.a = 4"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Maintenant ei contient un attribut *e1* , alrs que e2 qui est du même type ne contient pas d'attribut a."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "e2.a"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercice** :\n",
    "\n",
    "Créer un nouveau type d'objet ayant pour nom Voiture."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "class Voiture:\n",
    "    pass"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Créer 3 isntances v1, v2, v3 de Voiture."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "v1 = Voiture()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "v2 = Voiture()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "v3 = Voiture()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ensutite, ajoutez un attribut 'a' pour v1 contenant 1, un attribut 'b' conenant 1 pour v2 et un attribut 'a' contenant 4 pour v3."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "v1.a = 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "v2.b = 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "v3.a = 2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Vérifiez que ces objets ont tous des représentations mémoires différents :"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "v1 is v2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "v1 is v3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "v2 is v3"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Tapez la commande suivante :"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "g = v1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Est-ce que g et v1 sont des références vers le même objet en mémoire ? Vérifiez-le."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Prédéfinir des attributs et des fonctions\n",
    "\n",
    "On peut prédéfinir des attributs et des fonctions à l'interieur de la classe de l'objet.\n",
    "\n",
    "Pour définir une méthode de classe, il suffit de déclarer la fonction a l'interieur de la classe en mettant \"self\" comme premier paramètre.\n",
    "Quand une fonction est appelée depuis un objet $o$, le paramètre *self* contiendra l'objet $o$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "class Obj:\n",
    "    def f(self, p1, p2):\n",
    "        print(\"Call of %s.f(%s, %s)\"%(str(self),str(p1), str(p2)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "o1 = Obj()\n",
    "o2 = Obj()\n",
    "o1.f(1,2)\n",
    "o2.f(3,4)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Nous avons déjà vus qu'il y a des fonctions spéciales dans python. Une fonction spéciale commence toujours par deux \\_ . \n",
    "\n",
    "Par exemple, trouvez les fonctions suivantes : \\_\\_str\\_\\_, \\_\\_repr\\_\\_, \\_\\_int\\_\\_, \\_\\_plus\\_\\_, \\_\\_mult\\_\\_, etc ..."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "5.__str__"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "5.__init__"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "5.__repr__"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Essayez de trouvez la documentation de ces fonctions.\n",
    "\n",
    "La fonction __init__(self, ...) est spéciale car, cette fonction est exécutée quand un objet est créé. \n",
    "Usuellement, les attributs de l'objet sont déclarés dans cette fonction.\n",
    "\n",
    "On apelle cette fonction le constructeur de la classe."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "class Obj:\n",
    "    def __init__(self, a):\n",
    "        self.val = a^2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "e3 = Obj(4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "e3.val"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "e3 = Obj(2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "e3.val"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Héritage \n",
    "\n",
    "Parfois, on écrit plusieurs fois le même code. Du coup, on cherche à factoriser le code pour gagner du temps et éviter d'avoir les mêmes erreurs.\n",
    "\n",
    "Cela peut se faire avec l'héritage.\n",
    "\n",
    "Supposons qu'on veut implémenter une forme géometrique : un carré, un rectangle et un polygône convexe.\n",
    "Et supposons que l'on souhaite implementérr la fonction *number_of_coreners(self)* qui retourne le nombre de points situés sur le périmètre de la figure.\n",
    "\n",
    "Alors on peut procéder ainsi :"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "class convex_polygon:\n",
    "    def __init__( self, points ):\n",
    "        self.points = points\n",
    "    def number_of_corners(self):\n",
    "        return len(self.points)\n",
    "    \n",
    "class rectangle:\n",
    "    def __init__( self, points ):\n",
    "        self.points = points\n",
    "    def number_of_corners(self):\n",
    "        return len(self.points)\n",
    "    \n",
    "class square:\n",
    "    def __init__( self, points ):\n",
    "        self.points = points\n",
    "    def number_of_corners(self):\n",
    "        return len(self.points)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Il est clair que nous avons copier trois fois le même code.\n",
    "\n",
    "En fait, cela n'est pas nécessaire, car un carré est un réctangle spécial et un rectangle est un polygone convexe spécial.\n",
    "\n",
    "En fait, on peut éviter ces trois copies et expliquant à python le liens entre ces trois classes :"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "class convex_polygon:\n",
    "    def __init__( self, points ):\n",
    "        self.points = points\n",
    "    def number_of_corners(self):\n",
    "        return len(self.points)\n",
    "    \n",
    "class rectangle(convex_polygon):\n",
    "    pass\n",
    "    \n",
    "class square(rectangle):\n",
    "    pass"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "On dit alors que square hérite de rectangle et que rectangle hérite de convex_polygon.\n",
    "\n",
    "En fait, l'héritage est transitif et donc square hérite, par transitivité de convex_polygon.\n",
    "\n",
    "Plus précisément, en écrivant le code précédent, lorsque python créé un objet de type square, alors, il ajoute automatiquement les fonctions définies dans les classes parentes (rectangle et convex_polygon) dans l'objet qu'il vient de créer.\n",
    "\n",
    "En fait l'implémentation de la fonction number_of_corners() a été factorisée dans la classe convex_polygon."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "s = square([1,2,3,4])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "s.number_of_corners()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Vous avez surement remarqué que la fonction \\_\\_init\\_\\_ a elle aussi été factorisée.\n",
    "\n",
    "Cependant, ce n'est pas une bonnse idée dutiliser le même constructeur pour le carré, le rectangle et le polygone convexe.\n",
    "En effet, on veut que le constructeur de carré lève une erreure si l'utilisateur passe en paramètre plus de 4 points.\n",
    "\n",
    "On va donc redéfinir la fonction \\_\\_init\\_\\_ dans square. On dit que l'on surcharge \\_\\_init\\_\\_. \n",
    "Ainsi, l'implémentation de \\_\\_init\\_\\_ dans convex_polygon sera ignorée sauf si on l'apelle explictement à la main.\n",
    "\n",
    "Par exemple,"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "class convex_polygon:\n",
    "    def __init__( self, points ):\n",
    "        self.points = points\n",
    "    def number_of_corners(self):\n",
    "        return len(self.points)\n",
    "    \n",
    "class rectangle(convex_polygon):\n",
    "    pass\n",
    "\n",
    "class square(rectangle):\n",
    "    def __init__(self, points):\n",
    "        if len(points) != 4:\n",
    "            raise ValueError(\"Square should contain 4 points.\")\n",
    "        convex_polygon.__init__(self, points )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "s = square([1,2])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "s = square([1,2,3,4])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "s.points"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Maintnant l'architecture de notre class semble bonne.\n",
    "\n",
    "\n",
    "Ainsi des que l'on ajoute une nouvelle fonction dans convex_polygon, elle sera aussi disponnible pour square."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercice**: En fait, l'architecture n'est pas aussi bonne qu'on le dit. Pourquoi ? Modifez le code pour corriger cela."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercice** : Ecrire une fonction symetry_center(self) qui renvoie le centre de symétrie de nos objets."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercice** : Ajouter une fonction 'is_inside(self,point)' dans square, rectangle et convex_polygon."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "SageMath 7.3",
   "language": "",
   "name": "sagemath"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
