{ "nbformat": 4, "nbformat_minor": 0, "metadata": { "colab": { "name": "3_Advanced_Python.ipynb", "provenance": [], "collapsed_sections": [], "toc_visible": true, "include_colab_link": true }, "kernelspec": { "name": "python3", "display_name": "Python 3" } }, "cells": [ { "cell_type": "markdown", "metadata": { "id": "view-in-github", "colab_type": "text" }, "source": [ "\"Open" ] }, { "cell_type": "markdown", "metadata": { "id": "DfQ-d2onvooo", "colab_type": "text" }, "source": [ "Mickaël Tits\n", "CETIC\n", "mickael.tits@cetic.be" ] }, { "cell_type": "markdown", "metadata": { "id": "UAZ5qFKyZGMc", "colab_type": "text" }, "source": [ "# Chapitre 3 - Concepts avancés de programmation: exceptions, fonctions, objets" ] }, { "cell_type": "markdown", "metadata": { "id": "DO0zZ5fB6pC4", "colab_type": "text" }, "source": [ "# Gestion d'exceptions\n" ] }, { "cell_type": "markdown", "metadata": { "id": "jKkBgZcWduRW", "colab_type": "text" }, "source": [ "Comme dans la plupart des langages de programmations, lorsqu'une instruction ne peut être correctement effectuée, l'interpréteur renvoie un message d'erreur, et s'arrête. On appelle ça une **exception**. Le message d'erreur est en général une source d'information importante pour le développeur, car il permet de comprendre le bug et ainsi de le résoudre.\n", "\n", "On peut cependant contourner une exception pour permettre au programme de continuer, en définissant un comportement spécifique à adopter lorsqu'une exception survient." ] }, { "cell_type": "code", "metadata": { "id": "6hxrBp7L6-gm", "colab_type": "code", "colab": {} }, "source": [ "#Exemple d'exception\n", "x = 0\n", "inverse = 1/x" ], "execution_count": 0, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "6yAvFQXlmyzK", "colab_type": "text" }, "source": [ "* On peut rediriger toute exception:" ] }, { "cell_type": "code", "metadata": { "id": "G1caGhQbm1Zu", "colab_type": "code", "colab": {} }, "source": [ "x = 0\n", "try:\n", " inverse = 1/x\n", "except:\n", " inverse = float(\"inf\") \n", "print(inverse)" ], "execution_count": 0, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "81OEeBylm5sI", "colab_type": "text" }, "source": [ "* On peut vérifier quel type d'exception est survenue de cette manière:" ] }, { "cell_type": "code", "metadata": { "id": "jGOO536em-_A", "colab_type": "code", "colab": {} }, "source": [ "try:\n", " inverse = 1/x\n", "except Exception as e:\n", " print(e)\n", " inverse = float(\"inf\") \n", "print(inverse)" ], "execution_count": 0, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "8l6d8atbnGRY", "colab_type": "text" }, "source": [ "* On peut aussi gérer un type d'exception spécifique, pour modifier le comportement de façon pertinente selon le type d'exception.\n", "* Ca permet d'éviter de laisser passer un bug inattendu, ce qui rendrait le débogage plus compliqué" ] }, { "cell_type": "code", "metadata": { "id": "CE7wwY086sYm", "colab_type": "code", "colab": {} }, "source": [ "try:\n", " inverse = 1/y\n", "except ZeroDivisionError as e:\n", " inverse = float(\"inf\")\n", "print(inverse)" ], "execution_count": 0, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "oclBP29ZmB_6", "colab_type": "text" }, "source": [ "" ] }, { "cell_type": "markdown", "metadata": { "id": "hjY_GjCKdm1i", "colab_type": "text" }, "source": [ "# Fonctions \n" ] }, { "cell_type": "markdown", "metadata": { "id": "93r8gXyNdyMY", "colab_type": "text" }, "source": [ "* Les fonctions permettent de rendre le code modulaire (on ne réécrit pas 50x la même chose)\n", "* On définit d'abord une fonction, avant de pouvoir l'appeler. Pour la définir:\n", " * on utilise le mot-clé \"def\"\n", " * on définit l'**identifiant** et les **arguments** de la fonctions (appelés **signature** de la fonction). E.g.: `def my_function(x1, x2):`\n", "> **`my_function`** est l'identifiant de la fonction, et les éléments entre parenthèse **`x1, x2`** sont les arguments de la fonction.\n", "\n", "\n", " * on écrit ensuite dans **un bloc indenté d'instructions** en-dessous: ce sont les instructions de la fonction. Généralement, ces instructions réalisent des traitements utilisant les variables passées en arguments (sinon les arguments ne servent à rien).\n", " * le mot-clé **`return`** permet de renvoyer un résultat de la fonction et de sortir de la fonction. Ce mot-clé peut être utilisé plusieurs fois (par exemple pour retourner un résultat différent selon une condition).\n", "\n", "Exemple:\n", "\n", "\n", "```\n", "def my_function(x1, x2):\n", " out = x1*x2\n", " return out\n", "```\n", "\n", "\n", "On peut ensuite appeler une fonction de cette manière:\n", "\n", "\n", "```\n", "a = 6\n", "b = 7\n", "output = my_function(a, b)\n", "```\n", "Les variables `a` et `b` sont passées en arguments de la fonction `my_function`, et le résultat de la fonction (42) est renvoyé. La variable `output` est donc assignée à `42`.\n", "\n", "\n", "\n", "\n" ] }, { "cell_type": "code", "metadata": { "id": "Ni47Y48owqgD", "colab_type": "code", "colab": {} }, "source": [ "def factorial(n):\n", " \n", " \"\"\"\n", " Cette fonction permet de calculer le factoriel de n\n", " \"\"\"\n", " \n", " f = 1\n", "\n", " for i in range(1,n+1): \n", " f = f * i \n", "\n", " return f\n", "\n", "print(factorial(2), factorial(3), factorial(20) )\n" ], "execution_count": 0, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "B6NEo4XK33MQ", "colab_type": "text" }, "source": [ "* Les variables définies dans une fonction n'existe que lors de l'exécution de cette fonction\n", "* Les traitements effectués sur les variables ne sont valables que durant l'éxécution de la fonction" ] }, { "cell_type": "code", "metadata": { "id": "DVsoh-Qp3h4p", "colab_type": "code", "colab": {} }, "source": [ "x = 1\n", "y = 2\n", "\n", "def my_function(): \n", " \n", " print('x inside function =', x) #utilise la variable définie en-dehors de la fonction \n", "\n", " try:\n", " print(y)\n", " except Exception as exc:\n", " print(exc)\n", " #print(y) #renvoie une erreur car une fonction locale du même nom est déclarée après \n", " y = 4\n", " print('y inside function =', y) #imprime la variable locale\n", " \n", " z = 3 \n", " print('z inside function =', z) #imprime la variable locale\n", " \n", "\n", "my_function()\n", "\n", "\n", "print('x outside function =', x)\n", "print('y outside function =', y)\n", "try:\n", " print(z)\n", "except Exception as exc:\n", " print(exc)\n", "#print(z) # renvoie une erreur car la variable n'est pas définie en-dehors de la fonction\n", "\n" ], "execution_count": 0, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "Yl7v3tpUuMUg", "colab_type": "text" }, "source": [ "* On peut définir plusieurs arguments\n", "* On peut définir des aguments par défaut\n", "* Lors de l'appel de la fonction, on peut assigner uniquement certains arguments en utilisant leur identifiant" ] }, { "cell_type": "code", "metadata": { "id": "xXBrXymjuLIf", "colab_type": "code", "colab": {} }, "source": [ "def my_function(param1, param2 = \"param2\", param3 = \"param3\", param4 = \"param4\"):\n", " \n", " print(param1, param2, param3, param4)\n", "\n", "try:\n", " my_function()\n", "except Exception as e:\n", " print(\"Exception:\", e)\n", "\n", "my_function(1)\n", "my_function(1, 2)\n", "my_function(1, param3 = 2)\n" ], "execution_count": 0, "outputs": [] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "5kYcqRGedMQM" }, "source": [ "# Objets" ] }, { "cell_type": "markdown", "metadata": { "id": "lH2VAtf1lC5i", "colab_type": "text" }, "source": [ "## En Python, tout est objet" ] }, { "cell_type": "markdown", "metadata": { "id": "Nj47HiLJd1lB", "colab_type": "text" }, "source": [ "* En Python, tout est objet, que ce soit les types de base, les collections d'objets (les conteneurs), et même les fonctions.\n", "* On peut par exemple créer une liste contenant un ensemble de fonctions (pour par exemple manipuler efficacement un pipeline d'opérations, ou pour regrouper ensemble des fonctions ad-hoc, des paramètres et des résutlats de ces fonctions)." ] }, { "cell_type": "code", "metadata": { "id": "KNFvk9SfyMYr", "colab_type": "code", "colab": {} }, "source": [ "def square(x):\n", " return x**2\n", "def cube(x):\n", " return x**3\n", " \n", "my_list = [square, cube]\n", "operands = [4,5]\n", "operations = [my_list, operands]\n", "\n", "print(operations)\n", "\n", "print( [[func(op) for func in operations[0]] for op in operations[1]])" ], "execution_count": 0, "outputs": [] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "QIcduaIldMQM" }, "source": [ "## Les objets immuables sont simples\n", "Les types de base, tel que les **`int, float, string, bool`** sont **immuables**. Comme vu précédemment, les **`tuple`** sont également **immuables**. **Immuable** signifie qu'après leur création, on ne peut pas modifier les objets en mémoire. C'est pourquoi le code suivant donne une erreur :\n" ] }, { "cell_type": "code", "metadata": { "id": "iuctahqVjZSe", "colab_type": "code", "colab": {} }, "source": [ "my_tuple = (1,2,3)\n", "my_tuple[0] = 42" ], "execution_count": 0, "outputs": [] }, { "cell_type": "code", "metadata": { "id": "09ZOedRRkGWC", "colab_type": "code", "colab": {} }, "source": [ "my_int1 = 1\n", "#On crée une nouvelle étiquette (référence) vers l'objet 1\n", "my_int2 = my_int1\n", "#la fonction id renvoie l'adresse mémoire de l'objet (i.e. des données réellement stockées dans la mémoire RAM de l'ordinateur!)\n", "#Rien ne sert de stocker plusieurs fois l'entier 1 en mémoire, c'est pourquoi les deux variables renvoie au même endroit.\n", "print( my_int1, my_int2)\n", "print( id(my_int1) , id(my_int2) )\n", "\n", "#L'objet entier est immuable, donc lorsqu'on veut modifier my_int2, l'objet 1 n'est pas modifié en mémoire. A la place, l'étiquette \"my_int2\" est réassignée vers un nouvel objet en mémoire (en l'occurrence un objet de type entier et de valeur égale à 2)\n", "my_int2 += 1\n", "\n", "print( my_int1, my_int2)\n", "print( id(my_int1) , id(my_int2) )" ], "execution_count": 0, "outputs": [] }, { "cell_type": "code", "metadata": { "id": "pR_vzKpUl6Ux", "colab_type": "code", "colab": {} }, "source": [ "#Les tuples sont plus faciles (et rapides) à assigner\n", "%timeit my_tuple = (1,2,3, 4, True, \"hello\" )\n", "%timeit my_list = [1,2,3, 4, True, \"hello\" ]" ], "execution_count": 0, "outputs": [] }, { "cell_type": "code", "metadata": { "id": "bjMfywuilaMt", "colab_type": "code", "colab": {} }, "source": [ "#Les tuples sont plus légers que les listes\n", "import sys\n", "my_tuple = (1,2,3, 4, True, \"hello\" )\n", "my_list = list(my_tuple)\n", "print(sys.getsizeof( my_tuple ) , sys.getsizeof( my_list ) )" ], "execution_count": 0, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "wMFsq7dTjW82", "colab_type": "text" }, "source": [ "## Les objets mutables sont pratiques\n", "A l'inverse, les **`list, dict, set`** sont **mutables**, c'est-à-dire qu'on peut modifier l'objet lui-même.\n", "\n", "**Attention: lorsqu'on assigne une variable avec un objet mutable existant, cette variable devient une référence vers le même objet en mémoire!** Ainsi, le code ci-dessous va modifier à la fois les variables `my_list1` et `my_list2` :" ] }, { "cell_type": "code", "metadata": { "id": "TPsc0XCijngq", "colab_type": "code", "colab": {} }, "source": [ "my_list1 = [1,2,3]\n", "my_list2 = my_list1\n", "my_list2[0] = 42\n", "my_list1.append(4)\n", "my_list2 += [5]\n", "print(my_list1, my_list2)" ], "execution_count": 0, "outputs": [] }, { "cell_type": "code", "metadata": { "id": "fwiiEHLsnfNp", "colab_type": "code", "colab": {} }, "source": [ "#L'opérateur \"=\" réassigne my_list2, ce qui crée donc une nouvelle liste (différente de my_list1)\n", "my_list2 = my_list1+[6]\n", "my_list2 += [7]\n", "print(my_list1, my_list2)" ], "execution_count": 0, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "6aYYof9O9fKM", "colab_type": "text" }, "source": [ "## En Python, les arguments sont passés par référence des objets\n", "Lorsqu'on passe une variable en argument dans une fonction, c'est la référence de l'objet lui-même qui est passée à la fonction et non une copie (contrairement au langage C par exemple). ainsi, si l'objet passé en argument est mutable, celui-ci peut être modifié dans la fonction:" ] }, { "cell_type": "code", "metadata": { "id": "WH4iBm5N9-NP", "colab_type": "code", "colab": {} }, "source": [ "def modify(list_argument, int_argument): \n", " list_argument+=[4]\n", " int_argument += 4\n", " print(\"inside:\", list_argument, int_argument)\n", " \n", "my_int = 2\n", "my_list = [1,2,3]\n", "modify(my_list, my_int)\n", "\n", "#Un objet mutable est modifié en-dehors de la fonction, pas un objet immuable (puisque par définition ile ne peut être modifié)\n", "print(\"outside:\", my_list, my_int)\n" ], "execution_count": 0, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "H_i4bLqo903Z", "colab_type": "text" }, "source": [ "## Attributs et méthodes\n", "* Chaque objet peut contenir des attributs, et des méthodes qui permettent de réaliser des instructions prenant en compte du contexte de l'objet (c'est-à-dire de ses attributs)" ] }, { "cell_type": "code", "metadata": { "id": "v39LXlvq-4Ve", "colab_type": "code", "colab": {} }, "source": [ "my_float = 4.2\n", "help(my_float)" ], "execution_count": 0, "outputs": [] }, { "cell_type": "code", "metadata": { "id": "vTZI5h2I_bNP", "colab_type": "code", "colab": {} }, "source": [ "#Attributs d'un float\n", "print(my_float.real, my_float.imag)" ], "execution_count": 0, "outputs": [] }, { "cell_type": "code", "metadata": { "id": "gzyg0Bz9AdhG", "colab_type": "code", "colab": {} }, "source": [ "#Une méthode d'un float\n", "my_float = 4.0\n", "my_float.is_integer()" ], "execution_count": 0, "outputs": [] }, { "cell_type": "code", "metadata": { "id": "Pxaw816H-JTJ", "colab_type": "code", "colab": {} }, "source": [ "print(dir(my_list))" ], "execution_count": 0, "outputs": [] }, { "cell_type": "code", "metadata": { "id": "sTo6AQ6CBBvA", "colab_type": "code", "colab": {} }, "source": [ "my_list.index(2, 5)" ], "execution_count": 0, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "TkKuIgFDJ6DX", "colab_type": "text" }, "source": [ "## Les Classes\n", "* Les classes permettent de créer des nouveaux types d'objets, dont on peut définir les attributs et des méthodes.\n", "Les classes permettent d'**encapsuler** les différents attributs et méthodes dans une seule entité.\n", "L'**encapsulation** du code permet de rendre le code plus clair, et plus modulaire:\n", "* il est plus facile de modifier le comportement d'une classe sans impacter le reste du code\n", "* on peut facilement reprendre une classe et l'intégrer à un autre projet\n", "* on peut facilement ajouter des méthodes internes de vérification/validation/optimisation, transparentes pour le reste du programme.\n", "* on peut directement savoir quelles opératiosn peuvent être exécutées par un module (i.e. quels sont les \"responsabilités/services offerts\" par le module).\n" ] }, { "cell_type": "code", "metadata": { "id": "sKmSGLaoo1Fx", "colab_type": "code", "colab": {} }, "source": [ "class House:\n", " \n", " \"\"\"\n", " Cette classe permet de définir les propriétés d'une maison, donc l'adresse, le prix, la surface et le nombre de chambre.\n", " Elle permet de vérifier la validité desdonnées (validate()), d'afficher les informations sur la maison (display()), et de calculer le prix par m2 (compute_price_m2())\n", " \"\"\"\n", " \n", " def __init__(self, address, price, surface, rooms):\n", " \n", " self.address = address\n", " self.price = price\n", " self.surface = surface\n", " self.rooms = rooms\n", " \n", " #On vérifie la validité des données, et on les rend valides si possible, sinon on renvoie False (pas valide)\n", " self.is_valid = self.validate()\n", " \n", " def compute_price_m2(self):\n", " \n", " return self.price/self.surface\n", " \n", " def validate(self):\n", " \n", " \"\"\"\n", " Vérifie si l'objet est valide\n", " \"\"\" \n", " \n", " #Si le prix, la surface, ou le nombre de chambre ne sont pas des int, on essaye de les convertir, sinon pas valide\n", " try:\n", " self.price = int(self.price)\n", " self.surface = int(self.surface)\n", " self.rooms = int(self.rooms)\n", " return True\n", " except:\n", " return False\n", " \n", " def display(self):\n", " \n", " if self.is_valid:\n", " print(self.address, \":\", self.price, \"€, \", self.surface, \"m2\", self.rooms, \"rooms\")\n", " else:\n", " print(\"L'élément n'est pas un élément valide:\", self.address)\n", " " ], "execution_count": 0, "outputs": [] }, { "cell_type": "code", "metadata": { "id": "KzCn2ZsSp27T", "colab_type": "code", "colab": {} }, "source": [ "house1 = House(\"Rue de Bruxelles 42, Namur\", 300000, 120, 3)\n", "house1.display()\n", "#appeler un attribut (pas de parenthèses)\n", "print(house1.price)\n", "#appeler une méthode (parenthèses nécessaires)\n", "print(house1.compute_price_m2())" ], "execution_count": 0, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "fSuHaspBF8-P", "colab_type": "text" }, "source": [ "**Remarque:** On pourrait définir chaque méthode comme une fonction en-dehors du cadre de la classe, mais ça n'a pas d'intérêt puisque le processus est spécifique à la classe House. De plus, ça rend le code moins clair et rend possible une utilisation non-conforme, ce qui amène à des bugs:" ] }, { "cell_type": "code", "metadata": { "id": "HkRI8ztIF5eh", "colab_type": "code", "colab": {} }, "source": [ "def display(my_house):\n", "\n", " if my_house.is_valid:\n", " print(my_house.address, \":\", my_house.price, \"€, \", my_house.surface, \"m2\", my_house.rooms, \"rooms\")\n", " else:\n", " print(\"L'élément n'est pas un élément valide:\", my_house.address)\n", "\n", "display(house1)" ], "execution_count": 0, "outputs": [] }, { "cell_type": "code", "metadata": { "id": "sodmgTkDIF5_", "colab_type": "code", "colab": {} }, "source": [ "display([1,2,3])" ], "execution_count": 0, "outputs": [] }, { "cell_type": "code", "metadata": { "id": "KGV2JZkiqkVh", "colab_type": "code", "colab": {} }, "source": [ "#Attention, par défaut les classes sont mutables\n", "house2 = house1\n", "house2.price = 350000\n", "print(house1.price)" ], "execution_count": 0, "outputs": [] }, { "cell_type": "code", "metadata": { "id": "ieMd6ZiVvX9w", "colab_type": "code", "colab": {} }, "source": [ "#pour créer une copie d'un objet mutable, on peut utiliser un module Python: copy.copy\n", "from copy import copy\n", "\n", "house2 = copy(house1)\n", "\n", "house2.price = 500000\n", "print(house1.price)" ], "execution_count": 0, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "8wMDkbRovxK3", "colab_type": "text" }, "source": [ "**Remarque:** On peut tout à fait ajouter un attribut à un objet créé à partir d'une classe. Ce n'est cependant pas conseillé car les objets d'une même classe perdent alors leur homogénéité." ] }, { "cell_type": "code", "metadata": { "id": "7oC7T_kpJw9P", "colab_type": "code", "colab": {} }, "source": [ "house2.floors = 2\n", "print(house2.floors)\n" ], "execution_count": 0, "outputs": [] }, { "cell_type": "code", "metadata": { "id": "_5WiuEhSwPL-", "colab_type": "code", "colab": {} }, "source": [ "#L'hétérogénéité des objets d'une même classe rendent le programme plus compliqué, diminuent la clarté et augmentent les risques de bugs\n", "print(house1.floors)" ], "execution_count": 0, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "jF5PPWiarJ8V", "colab_type": "text" }, "source": [ "Maintenant que vous connaissez les concepts principaux du langage Python, vous pouvez passez au [Chapitre 4: Un exemple concret: analysons quelques biens immobiliers...](https://colab.research.google.com/github/titsitits/UNamur_Python_Analytics/blob/master/4_Example.ipynb)" ] } ] }