{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Les fonctions\n", "\n", "code sous licence creative commun CC BY-NC-SA BY Dominique Devedeux" ] }, { "cell_type": "raw", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ ":download:`Télécharger le pdf <./03-fonctions.pdf>`\n", "\n", ":download:`Télécharger le notebook <./03-fonctions-download.ipynb>`\n", "\n", ":download:`Lancer le notebook sur binder (lent) `" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Lorsqu'on écrit un programme, on a besoin de fonctions diverses et variées.\n", "\n", "- Les fonctions mathématiques arithmétiques (multiplication, addition,...) ou logiques (OU, ET, ...) classiques sont accessibles dans la bibliothèque de \"base\" de python et ne recquièrent aucune ligne de code supplémentaire pour y avoir accès.\n", "\n", "- Les fonctions mathématiques plus sophistiquées sont inclues dans des **bibliothèques** spécifiques disponibles qu'il faut importer avant de pouvoir utiliser ces fonctions.\n", "\n", "- On peut aussi avoir besoin de créer ses propres fonctions personnalisées.\n", "\n", "**Une bibliothèque est donc un ensemble de fonctions prédéfinies.**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Bibliothèques externes disponibles\n", "\n", "\n", "Une des grandes forces du langage Python réside dans le nombre important de bibliothèques logicielles externes disponibles. Celles-ci sont mises à disposition afin de pouvoir être utilisées sans avoir à les réécrire.\n", "\n", "Quelques exemples :\n", "\n", "- la librairie *math* contient entre autres les fonctions trigonométriques, la racine carrée, les recherches de PGCD, les factorielles ...\n", "- la librairie *random* permet d'avoir accès à de nombreuses fonctions en rapport avec la génération de nombres aléatoires,\n", "- la librairie *matplotlib* contient toutes les fonctions permettant de générer et de gérer l'affichage de graphiques,\n", "- la librairie *numpy* contient de nombreux outils mathématiques (trigonométriques, tableaux,...) et permet entre autres de modéliser des ensembles de valeurs.\n", "\n", "\n", " 1. L'importation des bibliothèques doit se faire en tête de programme\n", " 2. Pour importer une bibliothèque, il suffit d'écrire la ligne : **import nom_de_la_bibliothèque**\n", " 3. L'accès à la fonction s'effectue ainsi : **nom_de_la_bibliothèque.nom_de_la_fonction**\n", " 4. On peut aussi donner un petit surnom à la bibliothèque par souci de simplification : \n", " **import nom_de_la_bibliothèque as surnom**\n", " 5. On peut aussi parfois ne vouloir importer qu'une fonction spécifique et non pas la totalité d'une bibliothèque. Il suffit alors d'écrire : \n", " **from nom_de_la_bibliothèque import fonction**. Attention dans ce cas, l'accés à la fonction s'effectue ainsi : **nom_de_la_fonction**\n", " \n", " \n", "Quelques liens utiles... \n", "https://docs.python.org/fr/3/library/math.html\n", "\n", "https://docs.python.org/fr/3/library/random.html\n" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "-1.0\n" ] } ], "source": [ "# Importation de la bibliothèque math sans surnom\n", "import math\n", "print(math.cos(math.pi)) # permet d'avoir accès à la fonction cosinus ainsi qu'à la valeur de pi et d'afficher le résultat" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "7\n" ] } ], "source": [ "# Importation de la bibliothèque random avec surnom\n", "import random as rd\n", "print(rd.randint(1,10)) # la fonction randint permet d'afficher à l'écran un nombre aléatoire entier compris entre 1 et 10" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2\n" ] } ], "source": [ "# Importation de la seule fonction randint à partir de la bibliothèque random\n", "from random import randint\n", "print(randint(1,10)) # ne pas écrire random.randint() mais uniquement randint()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Fonctions personnalisées\n", "\n", "Un programme écrit « linéairement » est peu lisible . \n", "On préfère en général le décomposer en plusieurs sous-programmes, nommés fonctions. \n", "\n", "Une fonction est donc un ensemble d’instructions !\n", "\n", "Les avantages de définir des fonctions sont multiples :\n", "\n", "- le programme est plus lisible car architecturé, et se comprend plus facilement ;\n", "- le code est réutilisable ;\n", "- le programme est moins long : il suffit de définir une fonction pour effectuer une tâche précise, puis appeler cette fonction plusieurs fois si nécessaire ; \n", "- le programme est plus facile à corriger et à améliorer.\n", "\n", "\n", "### Création d'une fonction\n", "\n", "1. Les fonctions sont souvent écrites en tête de programme, après les imports de bibliothèques\n", "2. La définition d'une fonction commence toujours par la ligne **def nom_de_la_fonction():** (ne pas oublier les : à la fin, erreur classique)\n", "3. Les lignes de codes de cette fonction sont ensuite écrites en-dessous avec une indentation (décalage vers la droite).\n", "4. Lorsqu'il a besoin de cette fonction, le programme principal (PP) doit l'appeler : **nom_de_la_fonction()**\n", "5. Le PP peut avoir besoin d'échanger des informations avec la fonction.\n", "6. Il est conseillé de documenter la fonction en utilisant la syntaxe `\"\"\" (...) \"\"\"` juste après la déclaration de fonction\n", " \n", "Les exemples ci-après sont progressifs. Les fonctions étudiées n'ont pas d'autre intérêt que d'illustrer les différents échanges possibles entre le PP et la fonction." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Bien joué !\n" ] } ], "source": [ "#Dans ce premier exemple, il n'y a pas d'échange d'informations entre le PP et la fonction\n", "# Définition de la fonction nommée félicitations\n", "\n", "def félicitations() : # déclaration de la fonction\n", " \"\"\"\n", " Affiche un message de fécilicitations\n", " \"\"\"\n", " print(\"Bien joué !\") # ligne de code de la fonction\n", "\n", "#Programme principal\n", "félicitations() # Appel de la fonction " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Dans ce second exemple, le PP envoie le texte à imprimer à la fonction. Le PP envoie donc un paramètre (ici la variable texte1) à la fonction qui le range dans sa propre variable texte.\n", "![fonction1.png](images/03-01.png)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Bien joué !\n", "Perdu !\n" ] } ], "source": [ "# Définition de la fonction nommée affichage\n", "def affichage(texte) : # la variable texte contiendra la variable envoyée par le PP\n", " \"\"\"\n", " Affichage du texte donné en argument\n", " \n", " :param texte: texte à afficher\n", " \"\"\"\n", " print(texte) \n", "\n", "#Programme principal\n", "texte1 =\"Bien joué !\"\n", "affichage(texte1) # Premier appel de la fonction avec envoi de la variable texte1\n", "texte2 =\"Perdu !\"\n", "affichage(texte2) # Second appel de la fonction avec envoi de la variable texte2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Dans ce troisième exemple, le PP envoie deux paramètres (ici les variables a et b) à la fonction qui les range dans deux variables locales (ici x et y). Après avoir rempli son rôle, la fonction renvoie le résultat au PP. Celui-ci range alors le résultat dans sa variable res.\n", "![fonction2.png](images/03-02.png)" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "150.0\n" ] } ], "source": [ "# Définition de la fonction nommée calcul_moyenne\n", "def calcul_moyenne(x,y) : # la variable x contiendra la valeur 100 et y contiendra la valeur 200 \n", " \"\"\"\n", " Calcul de la moyenne de x et y\n", " \n", " :param x: valeur x\n", " :param y: valeur y\n", " :return: moyenne de x et y\n", " \"\"\"\n", " z=(x+y)/2\n", " return(z) # la fonction renvoie le résultat z au PP\n", "\n", "#Programme principal\n", "a=100\n", "b=200 \n", "res=calcul_moyenne(a,b) # Appel de la fonction avec envoi de 2 variables a et b ET stockage du résultat\n", "print(res) # renvoyé par la fonction dans la variable res." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Passage des arguments d'une fonction\n", "\n", "La lecture du code et de la documentation d'une fonction est utile pour connaître l'ordre et la signification des arguments d'une fonction, cependant ce n'est pas toujours pratique au milieu d'un long programme de s'y reporter, surtout dans le cas de fonctions avec un grand nombre d'arguments.\n", "\n", "Imaginons une fonction calculant la valeur du vecteur accélération moyen à partir des coordonnées de deux vecteurs vitesse $\\vec{v_1}$ et $\\vec{v_2}$ et de l'intervalle de temps $\\Delta t$\n", "\n", "On peut écrire cette fonction de plusieurs manières :\n", "\n", "```python\n", "def calcul_acceleration(vx1, vy1, vz1, vx2, vy2, vy2, dt):\n", "```\n", "\n", "\n", "```python\n", "def calcul_acceleration(vx1, vx2, vy1, vy2, vz1, vz2, dt):\n", "```\n", "\n", "```python\n", "def calcul_acceleration(dt, vx1, vx2, vy1, vy2, vz1, vz2):\n", "```\n", "\n", "etc..\n", "\n", "L'appel à la fonction en passant les arguments dans l'ordre nécessite de se rappeler quelle est la forme choisie pour l'ordre des arguments :\n", "\n", "```python\n", "# pas très parlant...\n", "calcul_acceleration(1, 0, 2, 3,0, 3, 0.04)\n", "```\n", "\n", "Heureusement, le python permet d'appeler une fonction en précisant le nom de chaque argument... et dans ce cas, il n'est plus nécessaire de donner les arguments dans l'ordre !\n", "\n", "```python\n", "# beaucoup plus clair\n", "calcul_acceleration(vx1=1, vy1=0, vz1=2, vx2=3, vy2=0, vz2=3, dt=0.04)\n", "```\n", "\n", "Mieux encore : il est possible de préciser dans la définition de la fonction des *valeurs par défaut* pour les arguments qui ne sont pas précisés lors de l'appel de la fonction.. si on travaille en 2 dimensions, par exemple, les valeurs en Z sont systématiquement nulles, on pert donc du temps à le préciser à chaque fois. \n", "\n", "Il suffit alors de définir la fonction en précisant la valeur de chaque argument :\n", "\n", "```python\n", "def calcul_acceleration(dt=0.04, vx1=0, vx2=0, vy1=0, vy2=0, vz1=0, vz2=0):\n", "```\n", "\n", "et l'appel de la fonction pour les vecteurs $\\vec{v_1}=(0,1,0)$ et $\\vec{v_2}=(1,0,0)$ pour l'intervalle \"classique\" de nos tables à coussin d'air $\\Delta t = 0.04$ se fait de la manière suivante :\n", "\n", "```python\n", "calcul_acceleration(v1y=1, v2x=1)\n", "```" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Calcul de l'accélération pour dt=1s, v1=(1,1,0) et v2=(2,0,0)\n", "Accélération : 1.4142135623730951 m/s^2\n", "Accélération : 1.4142135623730951 m/s^2\n", "Accélération : 1.4142135623730951 m/s^2\n", "Accélération : 1.4142135623730951 m/s^2\n" ] } ], "source": [ "# Exemple complet\n", "import math\n", "def calcul_acceleration(dt=0.04, vx1=0, vx2=0, vy1=0, vy2=0, vz1=0, vz2=0):\n", " \"\"\"\n", " Calcul de l'accélération moyenne à partir des coordonnées de\n", " deux vecteurs vitesse v1 et v2 et d'un intervalle de temps dt\n", " \n", " :param dt: intervalle de temps en secondes (défaut : 0,04 s)\n", " :param vx1: vecteur v1, coordonnée x (en mètres, défaut : 0 m)\n", " :param vx2: vecteur v2, coordonnée x (en mètres, défaut : 0 m)\n", " :param vy1: vecteur v1, coordonnée y (en mètres, défaut : 0 m)\n", " :param vy2: vecteur v2, coordonnée y (en mètres, défaut : 0 m)\n", " :param vz1: vecteur v1, coordonnée z (en mètres, défaut : 0 m)\n", " :param vz2: vecteur v2, coordonnée z (en mètres, défaut : 0 m)\n", " :return: \n", " \"\"\"\n", " return math.sqrt(((vx2-vx1)/dt)**2 + ((vy2-vy1)/dt)**2 + ((vz2-vz1)/dt)**2)\n", "\n", "# Différentes manières d'appeler cette fonction\n", "print(\"Calcul de l'accélération pour dt=1s, v1=(1,1,0) et v2=(2,0,0)\")\n", "# pas très clair, cauchemard de mémoire\n", "print(\"Accélération : \", calcul_acceleration(1, 1, 2, 1, 0, 0, 0) , \"m/s^2\")\n", "# peut être encore moins clair ?...\n", "print(\"Accélération : \", calcul_acceleration(1, 1, 2, 1) , \"m/s^2\")\n", "# mieux, non ?\n", "print(\"Accélération : \", calcul_acceleration(vx1=1, vy1=1, vx2=2, dt=1), \"m/s^2\")\n", "# on peut mixer les arguments par position et par nom\n", "print(\"Accélération : \", calcul_acceleration(1, vx1=1, vy1=1, vx2=2) , \"m/s^2\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Variables locales et globales (ou portée des variables)\n", "\n", "Note : La portée d'une variable correspond aux parties du programme où la variable est définie.\n", "\n", "### Définitions\n", "\n", "- Une variable définie à l'intérieur d'une fonction est une variable **locale**. Elle ne sera pas reconnue au sein d'une autre fonction ni au sein du programme principal.\n", "\n", "- Une variable définie au niveau du programme principal est une variable **globale**. Elle est reconnue partout dans le programme, même au sein des fonctions. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Le programme ci-dessous illustre ces deux premiers points :\n", "\n", "- la variable globale q est bien reconnue par la fonction qui peut ainsi l'afficher.\n", "- La variable locale p (définie au sein de la fonction) n'est pas reconnue dans le programme principal qui ne peut pas exécuter la demande d'affichage..\n", "\n", "![global1.png](images/03-03.png) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " ### Priorités\n", "\n", "Lorsque des variables locales et globales sont définies par un même nom au sein d'un programme, elles obéissent à des règles de priorité : \n", "\n", "- au sein d'une fonction, ce sont les variables définies localement qui ont la priorité sur les variables globales. Ainsi, lors de l'exécution d'une fonction, c'est la valeur de la variable locale qui est prise en compte.\n", "- au sein du programme principal, une variable globale conserve sa valeur initiale même si elle a été modifiée au sein d'une fonction." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "affichage 1 : p = 15\n", "affichage 2 : p = 20\n", "affichage 3 : p = 15\n" ] } ], "source": [ "def fonct1():\n", " p = 20 # ici p est une variable locale qui prend la priorité \n", " print(\"affichage 2 : p = \", p) \n", "\n", "p=15 # ici p est une variable globale dont la valeur est 15\n", "print(\"affichage 1 : p = \", p)\n", "fonct1() \n", "print(\"affichage 3 : p = \", p) # Au sein du programme principal, la variable globale p garde sa valeur initiale" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Avantages des variables locales\n", "\n", "Les variables locales permettent ainsi de compartimenter les actions. Cela signifie qu'un programme peut contenir quantités de fonctions sans se préoccuper le moins du monde des noms de variables qui y sont utilisées : en effet, ces variables étant locales (définies uniquement au sein d'une fonction), elles ne peuvent jamais interférer avec d'autres variables locales définies dans d'autres fonctions." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "affichage 1 : p = 20\n", "affichage 2 : p = 10\n" ] } ], "source": [ "def fonct1():\n", " p = 20 # ici p est une variable locale\n", " print(\"affichage 1 : p = \", p)\n", "\n", "def fonct2():\n", " p = 10 # ici p est une variable locale différente de celle créée dans fonct1\n", " print(\"affichage 2 : p = \", p)\n", "\n", "fonct1() # appel et exécution de la fonction fonct1\n", "fonct2() # appel et exécution de la fonction fonct2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Conversion d'une variable locale en variable globale - inconvénients\n", "\n", "On peut faire en sorte qu'une variable déclarée au sein d'une fonction soit malgré tout une variable globale. Pour cela, il suffit d'utiliser l'instruction **global**.\n", "\n", "Remarque : il est cependant préférable d'éviter l'utilisation de l'instruction **global** car c'est une source d'erreurs (on peut ainsi modifier le contenu d'une variable globale en croyant agir sur une variable locale).\n", "\n", "La sagesse recommande donc de suivre la règle suivante : ne jamais affecter dans une fonction une variable de même nom qu'une variable globale." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "affichage 1 : p = 20\n", "affichage 2 : q = None\n", "affichage 2 : r = 20\n" ] } ], "source": [ "def fonct1():\n", " global p # ici p est une variable globale \n", " p=20 # Notez l'absence de l'instruction return pour la variable globale!\n", "\n", "def fonct2():\n", " q=20 # ici q est une variable locale \n", " # Notez l'absence de l'instruction return et sa conséquence en ligne 16-17!\n", " \n", "def fonct3():\n", " r=20 # ici r est une variable locale \n", " return(r) # L'instruction return permet ici de renvoyer le contenu de la variable r au programmme principal\n", " \n", "fonct1()\n", "print(\"affichage 1 : p = \", p) # Au sein du programme principal, la variable globale p est bien reconnue alors qu'elle \n", " # n'était définie qu'au sein de la fonction fonct1.\n", "res2=fonct2()\n", "print(\"affichage 2 : q = \", res2) # La fonction fonct2 ne renvoie rien et la variable q est locale à la fonct2 : \n", " # La variable res2 ne contient donc aucune valeur !\n", " \n", "res3=fonct3() # La fonction fonct3 renvoie le contenu de la variable r locale \n", "print(\"affichage 2 : r = \", res3) # La variable res3 contient donc la valeur 20!" ] } ], "metadata": { "celltoolbar": "Format de la Cellule Texte Brut", "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.9" } }, "nbformat": 4, "nbformat_minor": 4 }