.. index:: ! instruction Enchaînements d'instructions ============================ Dans ce chapitre, nous abordons l'écriture d'un algorithme à proprement parler. Un algorithme est composé d'une série d'**instructions**, qui peuvent être enchaînées ou regroupées de différentes manières. .. index:: ! indentation Séquence ++++++++ La forme la plus simple d'enchaînement d'instructions composant une fonction est la `séquence`:index:. Les instructions sont écrites l'une après l'autre, séparées par un saut de ligne. Pour Python, il est indispensable qu'elles soient toutes au même niveau d'**indentation**, c'est-à-dire précédées du même nombre d'espaces\ [#indentation]_. Les instructions d'une séquence sont toutes exécutées, dans l'ordre ou elles sont écrites. Considérons par exemple l'algorithme suivant, calculant les trois premières puissances d'un nombre :: """ :entrée x: float, SAISI au clavier :pré-cond: Ø :sortie p2: float, AFFICHÉ à l'écran :sortie p3: float, AFFICHÉ à l'écran :sortie p4: float, AFFICHÉ à l'écran :post-cond: p2 = x², p3 = x³, p4 = x⁴ """ x = float(input("x=")) p2 = x*x p3 = p2*x p4 = p3*x print(p2, p3, p4) On peut représenter l'enchaînement des instructions de cet algorithme par le diagramme ci-dessous : .. graphviz:: digraph sequence { rankdir=LR; splines=line node [ shape=box ] i0 [ label="x = float(...)" ] i1 [ label="p2 = x*x" ] i2 [ label="p3 = p2*x" ] i3 [ label="p4 = p3*x" ] i4 [ label="print(p2, p3, p4)"] i0 -> i1 -> i2 -> i3 -> i4 } .. note:: La spécification `ne fait pas partie de l'algorithme `:ref: (elle décrit le « quoi », pas le « comment »). On l'encadre par des triples guillemets (``"""``) pour indiquer à Python qu'il peut ignorer cette partie lorsqu'il exécute l'agorithme. Condition +++++++++ Dans certains cas, il est nécessaire d'exécuter des instructions différentes selon qu'une condition est remplie ou non. Dans ce cas, on utilisera un *enchaînement conditionnel*. Celui-ci est composé ainsi : * première ligne : - le mot-clé ``if``, - l'expression booléenne représentant la condition, - le caractère ``:``. * enchaînement d'insructions à exécuter si la condition est vraie - toutes avec un niveau d'indentation supérieur à la première ligne. * le mot-clé ``else:`` - au même niveau d'indentation que la première ligne. * enchaînement d'instructions à exécuter si la condition est fausse - toutes avec un niveau d'indentation supérieur à la première ligne. NB : la clause ``else`` et les points suivants sont facultatifs. Les enchaînements suivant le ``if`` et le ``else`` ne sont bien sûr pas limités à des enchaînements séquentiels. Il peuvent comporter d'autres enchaînements conditionnels, ou des enchaînements répétitifs (cf. ci-dessous). La première instruction précédée du même nombre d'espaces (ou moins) que la première ligne (la ligne du ``if``) sera reconnue comme ne faisant pas partie de l'enchaînement conditionnel. Elle sera donc exécutée que la condition soit vraie ou non. Considérons par exemple l'algorithme suivant, qui calcule pour un nombre donné sa valeur absolue et son signe (représenté par le nombre 1 ou -1) :: """ :entrée x: float, AFFECTÉ précédemment :pré-cond: Ø :sortie signe: int, AFFICHÉ à l'écran :sortie valabs: float, AFFICHÉ à l'écran :post-cond: signe = +1 si x ≥ 0, -1 sinon :post-cond: valabs = |x| """ if x >= 0: signe = +1 valabs = x else: signe = -1 valabs = -x print(signe, valabs) On peut représenter l'enchaînement des instructions de cet algorithme par le diagramme ci-dessous : .. graphviz:: digraph condition { rankdir=LR; splines=line node [ shape=box ] t1 [ label="x >= 0", shape=diamond ] i1 [ label="signe = +1" ] i2 [ label="valabs = x" ] i3 [ label="signe = -1" ] i4 [ label="valabs = -x" ] j1 [ label="", shape=none, width=0, height=0 ] i5 [ label="print(signe, valabs)" ] t1 -> i1 [ taillabel=" oui" ] i1 -> i2; i2 -> j1 [ arrowhead=none ] t1 -> i3 [ taillabel=" non" ] i3 -> i4; i4 -> j1 [ arrowhead=none ] j1 -> i5 } Conditions multiples -------------------- Afin d'éviter d'imbriquer les ``if`` et les ``else`` les uns dans les autres, Python propose l'instruction ``elif`` (contraction de else if) qui se traduit en langage courant par "sinon si". Le ``elif`` doit être utilisé avec précaution et parcimonie, uniquement lorsque l'on est certain que toutes les conditions sont *exclusives*. En effet, les conditions sont vérifiées les unes après les autres jusqu'à ce qu'une condition soit vraie. Si une condition est vérifiée, les suivantes dans la liste ne sont pas examinées. Une suite de ``elif`` peut se terminer par un ``else`` qui sera donc traité si aucune condition n'a été vérifiée jusque là. Voici un exemple d'utilisation du ``elif``, qui donne le nombre de jours dans le mois donné (pour une année non bissextile) :: """ :entrée mois: int, AFFECTÉ précédemment :pré-cond: 1 ≤ mois ≤ 12 :sortie nbj: int, AFFECTÉ pour la suite :post-cond: nbj est le nombre de jours du mois dont le numéro est donné """ if mois == 2: nbj = 28 elif mois == 4 or mois == 6 or mois == 9 or mois == 11: nbj = 30 else: nbj = 31 Cette écriture est totalement équivalente à :: if mois == 2: nbj = 28 else: if mois == 4 or mois == 6 or mois == 9 or mois == 11: nbj = 30 else: nbj = 31 Répétition ++++++++++ Python supporte deux types d'enchaînements répétitifs, également appelés « enchaînements itératifs » ou « boucles » : la boucle ``while`` et la boucle ``for``. .. index:: boucle; while La boucle ``while`` ------------------- Dans certains cas, il est nécessaire d'exécuter les mêmes instructions un nombre de fois variable selon les valeurs d'entrée de l'algorithme. Plus précisément, on répétera ces instructions tant qu'une condition est remplie. Dans ce cas, on utilisera une boucle ``while``, qui est composée ainsi : * première ligne : - le mot-clé ``while``, - l'expression booléenne représentant la condition, - le caractère ``:``. * enchaînement d'instructions à répéter tant que la condition est vraie - toutes avec un niveau d'indentation supérieur à la première ligne. Ici encore, l'enchaînement d'instructions suivant le ``while`` peut comporter tous types d'enchaînements (séquentiel, conditionnel, répétitif). La première instruction précédée du même nombre d'espaces (ou moins) que la première ligne (la ligne du ``while``) sera reconnue comme ne faisant pas partie de la boucle. Elle sera donc exécutée dès que la condition devient fausse. Considérons par exemple l'algorithme suivant qui calcule le nombre de chiffres (en base 10) nécessaires à l'écriture d'un entier positif :: """ :entrée n: int, AFFECTÉ précédemment :pré-cond: n > 0 :sortie c: int, AFFICHÉ à l'écran :post-cond: n s'écrit en base 10 avec c chiffres """ c = 1 while n > 10: c = c+1 n = n//10 print(c) On peut représenter l'enchaînement des instructions de cet algorithme par le diagramme ci-dessous : .. graphviz:: digraph boucle_while { rankdir=LR; splines=ortho node [ shape=box ] i1 [ label="c = 1" ] t1 [ label="n > 10", shape=diamond ] i2 [ label="c = c+1" ] i3 [ label="n = n//10" ] i4 [ label="print(c)" ] i1 -> t1 t1 -> i2 [ taillabel="oui" ] i2 -> i3 i3 -> i4 [ style=invis ] i3:se -> t1:s [ constraint=false ] t1:n -> i4 [ taillabel="non "; constraint=false ] } Il est intéressant de remarquer que, selon les valeurs en entrée, les instructions de la boucle peuvent être exécutée plusieurs fois, une seule fois, voire pas du tout (si n=7, par exemple, dans l'algorithme ci-dessus). .. index:: boucle; for, ! itérable La boucle ``for`` ----------------- Certaines valeurs manipulées par un algorithme peuvent être vues comme « contenant » d'autres valeurs ; par exemple, une chaîne de caractères peut être vue comme une liste d'éléments plus simples que sont chacun de ses caractères. Ces valeurs complexes sont qualifiées d'**itérables** en Python, c'est-à-dire que l'on peut **itérer** dessus. Autrement dit, on peut parcourir leurs éléments un par un, dans l'ordre, et appliquer le même ensemble d'actions sur chacun d'eux. Ceci s'effectue avec la boucle ``for``, qui est composée ainsi : * première ligne : - le mot-clé ``for``, - un nom de variable - le mot-clé ``in``, - l'itérable sur lequel on veut boucler - le caractère ``:``. * enchaînement d'instructions à exécuter pour chaque élément de l'itérable - toutes avec un niveau d'indentation supérieur à la première ligne. La première instruction précédée du *même* nombre d'espaces (ou moins) que la première ligne (la ligne du ``for``) sera reconnue comme ne faisant pas partie de la boucle. La variable nommée après le ``for`` prendra successivement pour valeur chacun des éléments de l'itérable; et pour chacun d'entre eux, les instructions de la boucle seront exécutées. Considérons par exemple l'algorithme suivant qui retourne la chaîne de caractères « miroir » de la chaîne passée en entrée :: """ :entrée s: str, SAISI au clavier :pré-cond: Ø :sortie r: str, AFFICHÉ à l'écran :post-cond: r est la chaîne miroir de s """ s = input("s=") r = "" for c in s: r = c+r print(r) Par exemple, si l'utilisateur saisit la chaîne « épater », cet algorithme affichera « retapé ». .. index:: range Boucles avec ``range`` ---------------------- Il existe un cas particulier de boucle extrêmement fréquent : une boucle itérant sur des entiers successifs. Pour répondre à ce besoin, Python fournit la fonction ``range``, qui fournit un itérable contenant une séquence d'entier. Plus précisément : * ``range(i)`` itère sur l'intervalle [0,i[ * ``range(i, j)`` itère sur l'intervalle [i,j[ .. note:: Notez à nouveau l'interprétation des bornes en Python. La borne inférieure est toujours inclue, la borne supérieure est toujours exclue. Considérons par exemple l'algorithme suivant qui retourne la factorielle de l'entier `n` :: """ :entrée n: int, AFFECTÉ précédemment :pré-cond: n ≥ 0 :sortie f: int, AFFICHÉ au clavier :post-cond: f = n! = 1×2×3×...×(n-1)×n """ f = 1 for i in range(2, n+1): f = f*i print(f) Notons que ce type de boucle ``for``\ [#for-while]_ peut également s'écrire sous forme d'une boucle ``while`` : .. code-block:: :linenos: :emphasize-lines: 3,4,6 f = 1 i = 1 # 2 = valeur initiale du range while i < n+1: # n+1 = valeur finale du range f = f*i i = i+1 print(f) Le choix de l'une ou l'autre des écitures est une question de goût. La boucle ``for`` a l'avantage d'être plus concise, alors que la boucle ``while`` est plus explicite (notamment sur le fait qu'on ne rentre *pas* dans la boucle pour `i` = `n`\ +1). La boucle ``while`` est aussi plus générale : on peut faire varier `i` de différentes manières (par exemple, en le multipliant par deux à chaque itération), mais il est plus facile d'oublier l'initialisation de la variable (ligne 3 ci-dessus) ou sa modification en fin d'itération (ligne 6 ci-dessus). .. index:: ! fonction single: def single: return .. _fonction: Fonction ++++++++ Une **fonction** est un enchaînement d'instruction auquel on donne un nom pour pouvoir le réutiliser plus tard. Toute fonction résoud un problème précis, et est donc accompagnée de la *spécification* de ce problème. À titre d'exemple, voici comment on peut ré-écrire l'algorithme ci-dessus, qui calcule la factorielle de n'importe quel entier strictement positif :: def factorielle(n: int) -> int: """ :pré-cond: n > 0 :post-cond: retoune n! = 1×2×3×...×(n-1)×n """ f = 1 for i in range(2, n+1): f = f*i return f On constate que * la première ligne est composée ainsi : - le mot-clé ``def``, qui annonce que nous *définissons* une nouvelle fonction, - le nom de la fonction, - la liste des paramètres d'entrée, entre parenthèse et séparés par des virgules (le cas échéant), - chaque paramètre est décrit par son nom, suivi de deux points (``:``), suivi de son type de données, - le type de la valeur de retour, précédé par ``->``, - le caractère *deux points* (``:``). * Toutes les lignes suivantes ont un niveau d'indentation supérieur à la première. * La première ligne est suivie par la spécification du problème, encadrée par des triples guillemets (``"""``). En général, on peut ommettre les paramètres d'entrée et de sortie, puisque l'infomation est déjà présente dans la première ligne\ [#omission-paramètres]_. * La dernière ligne de l'algorithme comporte le mot-clé ``return``, suivi de la valeur à donner au paramètre de sortie. Lorsqu'une fonction possède plusieurs paramètres de sortie, leurs types (sur la première ligne) sont mis entre parenthèses et séparés par des virgules. Les valeurs retournées sont simplement séparées par des virgules : .. code-block:: :emphasize-lines: 1,10 def encadre_racine_carrée(x: float) -> (int, int): """ :pré-cond: x ≥ 0 :post-cond: inf ≤ √x < sup :post-cond: sup-inf = 1 """ inf = 0 sup = 0 # (...) séquence d'inscruction return inf, sup Appel de fonction ----------------- Une fois que l'on a défini une fonction, cette dernière fait alors partie des « capacités » de l'ordinateur. Pour indiquer à l'ordinateur qu'il doit *appeler* (ou utiliser) une fonction, on écrira une affectation dont : * la partie gauche comportera autant de variables que la fonction comporte de paramètres de sorties ; * la partie droite est constituée du nom de la fonction, suivi par la liste des valeurs des paramètres d'entrée, entre parenthèses et séparées, le cas échéant, par des virgules. Par exemple :: >>> bi, bs = encadre_racine_carrée(2.0) >>> k = max(2*j, i-1) # max() retourne la plus grande des valeurs passées en entrée Dans les exemples ci-dessus, on voit que les valeurs passées aux paramètres d'entrée peuvent être des expressions complexes. On constate aussi que les variables recevant les valeurs des paramètres de sortie ne sont pas tenues d'avoir le même nom que ces paramètres (s'ils sont nommés) ; c'est l'*ordre* des variables qui détermine leur correspondance avec les paramètres de sortie. Enfin, notons que, dans le cas particulier des fonctions n'ayant qu'un seul paramètre de sortie, l'appel à la fonction peut être utilisé directement dans une expression. Par exemple, au lieu d'écrire :: >>> i = factorielle(5) >>> j = i-1 on peut écrire directement :: >>> j = factorielle(5)-1 .. index:: ! portée single: variable; portée .. index:: single: paramètre d'entrée-sortie single: print single: fonction; pure single: fonction; procédure Types particuliers de fonction ------------------------------ On définit ici quelques notions qui sont parfois utiles pour distinguer certains types particuliers de fonctions. .. glossary:: effet de bord tout effet produit par une fonction en dehors des valeurs qu'elle retoure. Un exemple classique d'effet de bord est l'affichage d'information à l'écran. Dans le chapitre sur les `tableaux `:doc:, nous rencontrerons un autre type d'effet de bord, lié aux `paramètres d'entrée-sortie `:ref:. fonction pure toute fonction n'ayant aucun effet de bord, et dont les valeurs de retour dépendent exclusivement des valeurs passées en entrée. Des exemples de fonctions pures sont les fonctions mathématiques telles que *sinus* ou *factorielle*. procédure une fonction ne retournant rien. Elle ne comporte donc pas d'instruction `return`, ou alors le `return` n'est suivi d'aucune valeur. Par définition, une telle fonction doit avoir des effets de bord (sans quoi elle n'aurait aucun effet, et donc aucune utilité). Un exemple de procédure est la fonction `print`, dont le seul effet est d'afficher les valeurs qui lui sont passées en entrée. Puisqu'une procédure ne retourne aucune valeur, son appel se limitera au nom de la procédure suivi de ses paramètres, sans affectation :: >>> print("bonjour le monde") Notons qu'une fonction peut n'être ni une fonction pure, ni une procédure : * la fonction `input` (vue `précédemment `:ref:) retourne une valeur (ce n'est donc pas une procédure) mais elle a aussi des effets de bord en affichant un message à l'écran et en sollicitant une action de l'utilisateur (ce n'est donc pas une fonction pure). * La fonction `randrange(start, stop)` retourne un entier aléatoire compris entre `start` et `stop`. Puisqu'elle retourne une valeur, ce n'est pas une procédure. Mais cette valeur ne dépend pas uniquement des paramètres d'entrées : en appelant deux fois de suite `randrange(1, 7)`, on peut obtenir deux résultats différents. Ce n'est donc pas non plus une fonction pure. .. index:: variable intermédiaire single: paramètre d'entrée single: paramètre de sortie .. todo: cette différenciation de procédure/fonction de procédure est discutable; elle se base sur la présence ou non de valeur de retour, alors qu'elle pourrait se baser sur la présence d'effets de bords. Selon cette définition, un algo ayant un effet de bord *et* une valeur de retour serait une fonction. Selon l'autre définition, ce serait une procédure. Dans l'absolu, je préférerai l'autre définition, mais pédagogiquement, il me semble plus facile d'introduire les choses ainsi. Étant donné que cela ne fait une différence que dans le cas limite, je pense que c'est une concession acceptable. Variables d'une fonction ++++++++++++++++++++++++ On a présenté au `chapitre précédent `:doc: la notion de variable. Il convient de décrire ici les différentes catégories de variables utilisées dans une fonction. Elles sont au nombre de trois : * les variables correspondant aux paramètres d'entrée ; * les variables correspondant aux paramètres de sortie ; * les *variables intermédiaires*. Les variables correspondant aux paramètres d'entrée ont une particularité : elles ont déjà une valeur au début de la fonction (on verra dans la section `Appel de fonction`_ d'où vient cette valeur). Elles n'ont donc pas besoin d'être affectées, et on évitera en général de changer leur valeur\ [#changement-entrées]_. Les variables correspondant aux paramètres de sortie, quant à elles, doivent absolument être affectées dans la fonction, puisque c'est le rôle de cette dernière de déterminer leur valeur (rappelons que les paramètres de sortie décrivent la solution au problème que l'on cherche à résoudre). Ces valeurs sont transmises à l'appelant par l'instruction ``return`` à la fin de la fonction. Les variables intermédiaires, enfin, sont toutes les autres variables qui peuvent être nécessaires au calcul des paramètres de sortie. Par définition, elles n'ont pas de valeur initialement (elle doivent donc être affectées avant d'être utilisées), et leur valeur est « oubliée » à la fin de la fonction (puisqu'elles ne sont pas données à l'instruction ``return``). .. note:: Même si c'est souvent préférable, il n'est pas techniquement indispensable d'avoir une variable par paramètre de sortie. L'instruction ``return`` peut être suivie d'une expression complexe, impliquant des variables d'entrées et/ou des variables intermédiaires. Exemple :: def prochain_multiple_de_7(n:int) -> int: """ :pré-cond: n ≥ 0 :post-cond: retourne le plus petit multiple de 7 supérieur à n """ return (n//7+1)*7 Si l'on voit la fonction comme une boîte noire dont le rôle est d'effectuer une opération précise, alors : * les paramètres d'entrée contiennent les valeurs passées à la boîte noire ; * les paramètres de sortie contiennent les valeurs retournées par la boîte noire ; * les variables intermédiaires sont invisibles en dehors de la boîte noire. Imaginons que le rôle de notre boîte noire soit de déterminer quelles sont la plus petite et la plus grande valeur d'une liste de valeurs, ainsi que la moyenne des éléments de la liste. La liste de valeur est notre paramètre d'entrée, le minimum, le maximum et la moyenne sont les paramètres de sortie, et on imagine aisément que d'autres variables temporaires sont utilisées à l'intérieur de la fonction pour effectuer les calculs intermédiaires (par exemple, la somme des valeurs et le nombre de valeurs, nécessaires au calcul de la moyenne). .. index:: fonction; appel, capacité Portée des variables -------------------- Les variables utilisées dans une fonction sont *propres* à cette fonction. Elles ne sont ni visibles, ni utilisables depuis d'autres fonctions, même si ces dernière définissent une variable ayant le même nom. On dit que la *portée* de la variable est limitée à la fonction qui la définit\ [#portées]_. Considérons l'exemple ci-dessous : .. code-block:: python :linenos: def nb_chiffres(n: int) -> int: """ :pré-cond: n > 0 :post-cond: retourne le nombre de chiffres nécessaires pour écrire n en base 10 """ c = 1 while n > 10: c = c+1 n = n//10 return c def total_chiffres_fact(n: int) -> int: """ :pré-cond: n > 0 :post-cond: retournr le nombre total de chiffres nécessaires pour écrire les factorielles de tous les entiers entre 1 et n """ f = 1 c = 0 for i in range(1, n+1): f = f*i c = c+nb_chiffres(f) return c La fonction ``total_chiffres_fact`` appelle la fonction ``nb_chiffres``. On remarquera que ces deux fonctions utilisent les mêmes noms de variable (`n`, `c`). Cependant, il n'y a aucune interaction entre la valeur de `n` (respectivement de `c`) dans ``nb_chiffres`` et la valeur de `n` (respectivement de `c`) dans ``total_chiffres_fact``. Il faut imaginer que, lorsque l'ordinateur exécute ``total_chiffres_fact``, il stocke les valeurs de `n`, `c` et `f` dans un emplacement E1 de sa mémoire, qui est dédié aux variables définies dans cette fonction. Lorsqu'à la ligne 13, on lui demande d'exécuter la fonction ``nb_chiffres``, il laisse de coté l'emplacement E1 et commence à travailler sur un emplacement E2, dédié aux variables de ``nb_chiffres``, dans lequel il va stocker les valeurs de `n` et `c` de ``nb_chiffres``. Lorsque cette dernière se termine, l'emplacement E2 est supprimé, et l'ordinateur travaille à nouveau sur l'emplacement E1 pour terminer l'exécution de ``total_chiffres_fact``, ou `n` et `c` ont gardé les valeurs qu'elles avaient juste avant l'appel à ``nb_chiffres``. .. _fig-portee: .. figure:: ../_static/porteeVariables.png Les variables de chaque fonction existent à des endroits différents de la mémoire, même lorsqu'elles ont le même nom. Vous pouvez faire la simulation_ sur Pythontutor_ pour mieux comprendre comment sont gérés les espaces mémoires .. _simulation: http://pythontutor.com/visualize.html#code=def%20nb_chiffres%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%0A%20%20%20%20%3Apr%C3%A9-cond%3A%20%20n%20%3E%200%0A%20%20%20%20%3Apost-cond%3A%20retourne%20le%20nombre%20de%20chiffres%20n%C3%A9cessaires%20pour%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%C3%A9crire%20n%20en%20base%2010%0A%20%20%20%20%22%22%22%0A%20%20%20%20c%20%3D%201%0A%20%20%20%20while%20n%20%3E%2010%3A%0A%20%20%20%20%20%20%20%20c%20%3D%20c%2B1%0A%20%20%20%20%20%20%20%20n%20%3D%20n//10%0A%20%20%20%20return%20c%0A%0Adef%20total_chiffres_fact%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%0A%20%20%20%20%3Apr%C3%A9-cond%3A%20%20n%20%3E%200%0A%20%20%20%20%3Apost-cond%3A%20retournr%20le%20nombre%20total%20de%20chiffres%20n%C3%A9cessaires%20pour%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%C3%A9crire%20les%20factorielles%20de%20tous%20les%20entiers%20entre%201%20et%20n%0A%20%20%20%20%22%22%22%0A%20%20%20%20f%20%3D%201%0A%20%20%20%20c%20%3D%200%0A%20%20%20%20for%20i%20in%20range%281,%20n%2B1%29%3A%0A%20%20%20%20%20%20%20%20f%20%3D%20f*i%0A%20%20%20%20%20%20%20%20c%20%3D%20c%2Bnb_chiffres%28f%29%0A%20%20%20%20return%20c%0A%20%20%20%20%0Atotal_chiffres_fact%285%29&cumulative=false&curInstr=12&heapPrimitives=false&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false .. _Pythontutor: http://pythontutor.com/ Il est cependant important de détailler ce qui se passe aux moments du passage de E1 à E2, et du retour de E2 à E1 : * au moment de passer de la fonction appelante (E1) à la fonction appelée (E2), les expressions passées aux paramètres d'entrée (entre les parenthèses) sont calculées avec les variables de E1, et leurs valeurs sont affectées aux variables correspondantes dans E2 ; * au moment de revenir de la fonction appelée à la fonction appelante, les valeurs des paramètres de sortie sont affectées aux variables correspondantes dans E1, ou substituées à l'appel de fonction si celui-ci est utilisé directement dans une expression\ [#return]_. Considérons l'exemple ci-dessus, où on aurait appelé la fonction ``total_chiffres_fact`` avec `n`\=5. Au premier passage à la ligne 13, les variables de ``total_chiffres_fact`` ont les valeurs suivantes : `n`\=5, `c`\=0, `i`\=1 et `f`\=1. À ce moment, l'ordinateur calcule les valeurs à passer aux paramètres d'entrée de ``nb_chiffres``, en l'occurrence un seul paramètre, dont la valeur est 1 (valeur de `f`). L'emplacement mémoire qui contiendra les variables de ``nb_chiffres`` est donc initialisé avec `n`\=1. À la fin de l'exécution de ``nb_chiffres``, ses variables ont pour valeur `n`\=1 et `c`\=1 (cf `figure %s `:numref:). Comme ``nb_chiffres`` est utilisée directement dans une expression, la valeur du paramètre de sortie `c` est substituée à l'appel de fonction, donc la ligne 13 de ``total_chiffres_fact`` revient ici à calculer :: c = c+1 dans l'emplacement mémoire de ``total_chiffres_fact``, donc avec `c`\=0. Notons aussi que la valeur de `n` dans ``total_chiffres_fact`` est toujours 5, et n'a pas été influencée par le fait que ``nb_chiffres`` utilisait une variable du même nom avec une valeur différente. .. index:: modularité L'apparente complexité de ce processus est en fait une simplification : elle permet au programmeur d'une fonction `f` de *ne pas se soucier* des noms de variables utilisés dans les autres fonctions (celles appelées par `f` comme celles qui appellent `f`). Il favorise donc la *modularité* du code. .. rubric:: Notes de bas de page .. [#indentation] Dans la plupart des autres langages de programmation, l'indentation n'est pas obligatoire. Cependant, elle est un aspect important de l'écriture d'algorithmes. Une indentation claire et cohérente facilite grandement la lecture et la compréhension. Vous devriez donc toujours la considérer comme un obligation, même dans des langages qui ne l'imposent pas. Lorsqu'on écrit des algorithmes à la main sur papier, il peut être utile de matérialiser les niveaux d'indentation par des lignes verticales. .. [#for-while] En fait, *n'importe quelle* boucle ``for`` peut s'écrire sous forme d'une boucle ``while``. .. [#omission-paramètres] En fait, la première ligne n'indique que les paramètres d'entrée « PASSÉS en paramètres », et les paramètres de sortie « RETOURNÉ ». Si la fonction attend d'autres entrées (par exemple saisies au clavier) ou fournit d'autres sorties (par exemple affichées à l'écran), il conviendra de déclarer ces paramètres supplémentaire explicitement. .. [#retourner] On dit d'ailleurs couramment qu'une fonction *retourne* ses paramètres de sortie. .. [#changement-entrées] Ceci n'est en rien une contrainte technique, mais plutôt une règle de bonne pratique (cf. `bonnes_pratiques`:doc:). Et même cette règle peut, dans certains cas, souffrir des exceptions. .. [#portées] Il est possible, en Python et dans d'autres langages, de définir des variables avec une portée plus petite ou plus grande, mais nous ne traiterons pas de cela dans ce cours. .. [#return] En fait, c'est l'expression passée à ``return`` qui est systématiquement substituée à l'appel de la fonction. Python autorise une utilisation beaucoup plus souple de l'instruction ``return`` que celle préconisée dans ce cours.