.. _tut-fonctions: ******************** Fonctions et modules ******************** Une vidéo de présentation des fonctions... .. raw:: html Les fonctions permettent de structurer le code, de le rendre plus lisible, d'en faciliter la maintenance, en évitant la redondance. L'utilisation de fonctions (et, on le verra plus tard, de classes) fait parties des bonnes pratiques de la programmation, et est donc très vivement encouragée. Les fonctions ============= Définition ---------- Une fonction se définit avec l'instruction :keyword:`def` suivie du nom de la fonction et de la liste de ses arguments. Comme pour les instructions :keyword:`if`, :keyword:`for` et :keyword:`while` le corps de la fonction est constitué d'un bloc d'instructions indenté. Prenons l'exemple de la `suite de Fibonacci `_ dont le code est défini dans un fichier :download:`fonctions.py <../files/fonctions.py>`. .. code-block:: python :linenos: # filename : fonctions.py def fib(n): a, b = 0, 1 while a < n: print(a, end=' ') a, b = b, a+b print() .. note:: - En Python, le passage des arguments ne se fait ni par copie (comme en C) ni par référence (comme en Java). Il se fait par affectation. L'article `Pass-by-value, reference, and assignment `_ fait le point sur le sujet ; - Il n'est pas nécessaire de préciser le type des paramètres ni le type de retour. Il est cependant possible d'associer ces informations à la définition de la fonction avec les `annotations `_. Exécution --------- Exécuter le code ci dessus à partir de la console ne produit rien:: $ python fonctions.py $ C'est tout à fait normal, car le code ne contient pour le moment que la définition de l'objet :class:`fib` (en Python tout est objet) de type fonction, comme on peut le constater dans l'interpréteur python:: >>> import fonctions >>> fonctions.fib >>> type(fonctions.fib) Structuration ------------- L'utilisation d'une fonction se décompose en deux étapes principales : #. la *définition* de la fonction, qui décrit la transformation des arguments (données d'entrée) en un objet retourné (donnée de sortie) ; #. et son *appel* qui applique cette transformation à des paramètres concrets. Ce qui précède relève de la première étape. La deuxième étape est illustrée ci dessous, en appelant la fonction avec le paramètre ``50`` :: >>> fonctions.fib(50) 0 1 1 2 3 5 8 13 21 34 On peut également inclure l'appel de la fonction dans le programme. .. code-block:: python :linenos: :emphasize-lines: 9 # filename : fonctions.py def fib(n): a, b = 0, 1 while a < n: print(a, end=' ') a, b = b, a+b print() fib(50) Cette fois l'exécution du script produit le résultat attendu dans le terminal:: $ python fonctions.py 0 1 1 2 3 5 8 13 21 34 .. important:: Par la suite, les programmes seront tous structurés de façon identique et devront comporter 2 parties clairement identifiées : #. définition de la fonction ; #. appel. Valeur de retour ---------------- La valeur de retour est indiquée par l'instruction (optionnelle) :keyword:`return`. Si celle ci n'est pas présente, la valeur par défaut ``None`` est retournée: .. code-block:: python :linenos: # filename : fonctions-2.py def fib(n): a, b = 0, 1 while a < n: print(a, end=' ') a, b = b, a+b print() print(fib(50)) A la ligne 9, séquentiellement, on a: - un appel à ``fib(50)`` qui produit l'affichage de la suite ; - puis la valeur de retour de la fonction :func:`fib` est passée à :func:`print`. Puisque la fonction ne possède pas d'instruction :keyword:`return` la valeur de retour est ici ``None``, qui est affichée dans le terminal. L'exécution du code produit le résultat attendu:: $ python fonctions2.py 0 1 1 2 3 5 8 13 21 34 None Contrairement à l'exemple ci dessus, c'est une bonne pratique que de spécifier **explicitement** une valeur de retour pour les fonctions. Ici on préférera donc écrire : .. code-block:: python :linenos: :emphasize-lines: 8 # filename : fonctions-2.py def fib(n): a, b = 0, 1 while a < n: print(a, end=' ') a, b = b, a+b print() return None print(fib(50)) Dans la grande majorité des cas, la valeur de retour existe, comme c'est le cas ci dessous. .. code-block:: python :linenos: # filename : square.py def square(x): return x**2 print(square(7)) On retrouve la structuration évoquée ci dessus : #. définition : lignes 2 et 3 ; #. appel : ligne 5. Lorsqu'on exécute le code:: $ python square.py 49 Ici la fonction :func:`square` ne produit aucun affichage par elle même. Son appel retourne une valeur qui est passée à la fonction :func:`print` qui se charge de l'affichage. Ce sera une bonne pratique de confier : - à la fonction : la construction d'un objet ; - au code appelant : l'affichage éventuel. Portée des variables -------------------- Les variables définies à l'intérieur de la fonction ont une portée locale. On considère le programme suivant, en observant la structuration : .. code-block:: python :linenos: # 04-portee.py def f(): print("entering f()...") x = 'black' print("in f(), x =", x) print('exiting f()') return None x = 'white' print("before f(), x =", x) f() print("after f(), x =", x) .. admonition:: Exercice... Identifier la portion de code concerné par : - la définition de la fonction ; - son appel. Identifier la séquence ordonnée des lignes exécutées lorsqu'on lance le programme avec :command:`python 04-portee.py`. On constate ici que la valeur de ``x`` n'est pas modifiée par l'appel de la fonction:: $ python 04-portee.py before f(), x = white entering f()... in f(), x = black exiting f() after f(), x = white Les variables définies à l'extérieur de la fonction sont accessibles à l'intérieur. .. code-block:: python :linenos: def g(): print('entering g()') print("in g(), x =", x) print('exiting g()') return None x = 'white' print("before g(), x =", x) g() print("after g(), x =", x) L'affichage montre bien que la variable ``x`` est accessible à l'intérieur de la fonction, alors qu'elle est définie à l'extérieur:: before g(), x = white entering g() in g(), x = white exiting g() after g(), x = white Bien que ce ne soit pas recommandé, il est possible de modifier une variable définie à l'extérieur du corps de la fonction, en utilisant le mot clé :keyword:`global`: .. code-block:: python :linenos: def h(): print('entering h()') global x print("in h(), before redefinition, x =", x) x = 'blue' print("in h(), after redefinition, x =", x) print('exiting h()') x = 'white' print("before h(), x = ", x) h() print("after h(), x = ", x) La variable ``x`` définie à l'extérieur de la fonction est bien modifiée par celle ci:: before h(), x = white entering h() in h(), before redefinition, x = white in h(), after redefinition, x = blue exiting h() after h(), x = blue Les modules =========== Un fichier contenant des instructions Python s'appelle un **module**. Un module peut contenir une ou plusieurs fonctions. Contenu et structure -------------------- Un module peut provenir : - du *core language*, c'est à dire être accessible sans opération particulière ; - de la bibliothèque standard Python et être accessible après une simple instruction :keyword:`import` ; - d'une tierce partie et être accessible après installation du module externe et l'instruction :keyword:`import` ; On développe ses propres modules en faisant appel aux modules pré existants décrits ci dessus. A l'import, l'ensemble du code du module est exécuté, ce qui n'est pas toujours souhaitable lorsqu'un module comprend à la fois la définition des fonctions mais également leur utilisation. Ceci semble contradictoire avec la consigne donnée plus haut, de regrouper dans un seul et même fichier (module) la définition des fonctions et leur appel. Mais il n'en est rien car Python a prévu une construction spéciale pour "protéger" la partie du code qui correspond à l'appel des fonctions. .. code-block:: python # mymodule.py def f(): print("inside f()") if __name__ == '__main__': print("calling f()") f() Lorsque le module est importé (au travers de l'instruction :keyword:`import`) l'ensemble des fonctions est chargé en mémoire mais le code encapsulé dans la construction :keyword:`if` n'est pas exécuté:: import mymodule ne produit aucun affichage mais la fonction :func:`f` est utilisable dans la suite du script. A contrario, si le module est exécuté:: $ python mymodule.py Le code encapsulé dans la construction :keyword:`if` est exécuté:: calling f() inside f() C'est une possibilité très intéressante car elle permet l'utilisation du même module lors de la phase de conception et lors de son utilisation. **Cette bonne pratique est très vivement encouragée**. Structuration conseillée ------------------------ Pour structurer encore plus efficacement le programme, on utilisera l'organisation suivante #. Imports et définition des variables globales ; #. Définition des fonctions secondaires ; #. Définition de la fonction principale :func:`main` qui appelle les fonctions secondaires ; #. Appel protégé de la fonction principale :func:`main`. Voici un exemple de module respectant cette organisation. .. code-block:: python :linenos: # mymodule.py # 1. imports et définition des variables globales import math # 2. définition des fonctions secondaires def circle_area(diameter): return math.pi * diameter**2 / 4 # 3. définition de la fonction principale def main(): print(circle_area(10)) # 4. appel protégé de la fonction principale if __name__ == '__main__': main() .. admonition:: Exercice... Identifier la séquence ordonnée des lignes exécutées lorsqu'on lance le programme ci dessus. Importation ----------- Il existe deux manières d'importer et donc d'utiliser une fonction appartenant à un module. Chacune d'entre elles vise à identifier cette fonction de façon univoque: #. importation du module complet et identification de la fonction lors de l'appel ; #. importation d'une fonction identifiée du module. Dans le premier cas, on importe le module entier, et le lien entre la fonction et le module est précisé lors de l'appel:: >>> import mymodule >>> mymodule.f() Dans le deuxième cas, le lien entre la fonction et le module est précisé à l'import, et il n'est pas nécessaire de le préciser lors de l'appel:: >>> from mymodule import f >>> f() Lorsqu'il n'y aura pas d'ambiguïté sur le nom de la fonction, on choisira de préférence la seconde un peu plus concise. Les docstrings ============== Une bonne pratique est de documenter son code de façon générale, et les fonctions en particulier. Python met en oeuvre la notion de ``docstring`` qui permet d'exploiter une chaîne de caractère définie sur la ligne suivant la définition de la fonction: .. code-block:: python :linenos: # fact.py def fact(n): """ Retourne la factorielle de n. Args: n: valeur entiere positive Returns: fact(n) : n*(n-1)* ... * 2 """ if n <= 0: return 'n must be strictly positive' if n <= 2: return n return n*fact(n-1) On obtient ainsi une aide sur la fonction avec :func:`help`:: >>> from fact import fact # from fact.py module import fact() function >>> fact(5) 120 >>> help(fact) Help on function fact in module fonctions: fact(n) Retourne la factorielle de n. Args: n: valeur entiere positive Returns: fact(n) : n*(n-1)* ... * 2 .. warning:: La docstring doit débuter sur la ligne immédiatement après la définition de la fonction. Les doctests ============ On peut inclure dans les ``docstring`` des sessions interactives qui seront exécutées lors de l'appel. Ces sessions interactives comprennent deux parties : - l'appel de la fonction ; - et le résultat attendu. Les résultats obtenus lors de l'appel seront automatiquement comparés aux résultats attendus. .. code-block:: python :linenos: # fact.py def fact(n): """ Retourne la factorielle de n. Args: n: valeur entière >= 2 Returns: fact(n) : n*(n-1)* ... * 2 >>> fact(-1) 'n must be strictly positive' >>> fact(0) 'n must be strictly positive' >>> fact(1) 1 >>> fact(2) 2 >>> fact(5) 120 >>> fact(10) 3628800 """ if n <= 0: return 'n must be strictly positive' if n <= 2: return n return n*fact(n-1) Les tests sont lancés en faisant appel au module :mod:`doctest` de la bibliothèque standard. Lorsque les tests passent, aucun affichage supplémentaire n'est produit:: $ python -m doctest fact.py $ On peut cependant obtenir un affichage plus détaillé avec l'option ``-v``:: $ python -m doctest fact.py -v Trying: fact(-1) Expecting: 'n must be strictly positive' ok Trying: fact(0) Expecting: 'n must be strictly positive' ok Trying: fact(1) Expecting: 1 ok Trying: fact(2) Expecting: 2 ok Trying: fact(5) Expecting: 120 ok Trying: fact(10) Expecting: 3628800 ok 1 items had no tests: fact 1 items passed all tests: 6 tests in fact.fact 6 tests in 2 items. 6 passed and 0 failed. Test passed. Si la fonction est mal conçue, un ou plusieurs tests peuvent échouer. .. code-block:: python :linenos: :emphasize-lines: 25 # fact.py def fact(n): """ Retourne la factorielle de n. Args: n: valeur entière >= 2 Returns: fact(n) : n*(n-1)* ... * 2 >>> fact(-1) 'n must be strictly positive' >>> fact(0) 'n must be strictly positive' >>> fact(1) 1 >>> fact(2) 2 >>> fact(5) 120 >>> fact(10) 3628800 """ if n < 0: return 'n must be strictly positive' if n <= 2: return n return n*fact(n-1) Ici, le test ``fact(0)`` échoue:: > python -m doctest fact.py ********************************************************************** File "fact.py", line 13, in fact.fact Failed example: fact(0) Expected: 'n must be strictly positive' Got: 0 ********************************************************************** 1 items had failures: 1 of 6 in fact.fact ***Test Failed*** 1 failures. On peut également intégrer l'exécution des tests dans le module lui même. .. code-block:: python import doctest if __name__ == '__main__': doctest.testmod() L'utilisation des :mod:`doctest` est une bonne pratique et à ce titre, **elle est vivement recommandée**. Ce qu'il faut retenir ===================== .. quiz:: quizz-04 :title: Fonctions et modules - L'instruction :quiz:`{"type":"FB","answer":"def", "size":3}` est utilisée pour la définition d'une fonction - :quiz:`{"type":"TF","answer":"T"}` L'instruction :keyword:`return` permet de retourner un objet - :quiz:`{"type":"TF","answer":"T"}` L'instruction :keyword:`return` est optionnelle - :quiz:`{"type":"TF","answer":"F"}` Si une fonction ne retourne rien, l'objet renvoyé est ``Null`` - Les 2 phases (dans l'ordre logique) de l'utilisation d'une fonction sont la :quiz:`{"type":"FB","answer":"définition", "size":3, "flags":"fuzzy"}` et l':quiz:`{"type":"FB","answer":"appel", "size":3}` - :quiz:`{"type":"TF","answer":"T"}` La définition et l'appel de la fonction peuvent se trouver dans le même fichier - :quiz:`{"type":"TF","answer":"T"}` La définition et l'appel de la fonction peuvent se trouver dans des fichiers différents - :quiz:`{"type":"TF","answer":"F"}` Les variables définies à l'intérieur de la fonction sont accessibles depuis le code d'appel - :quiz:`{"type":"TF","answer":"T"}` Les variables définies dans le code d'appel sont accessibles à l'intérieur de la fonction - :quiz:`{"type":"TF","answer":"T"}` Un module est un fichier Python - :quiz:`{"type":"TF","answer":"F"}` Le code encapsulé dans une construction ``if __name__`` est exécuté si le module est importé - :quiz:`{"type":"TF","answer":"T"}` Le code encapsulé dans une construction ``if __name__`` est exécuté si le module est exécuté depuis le terminal - Il existe :quiz:`{"type":"FB","answer":"2", "size":3}` façons d'importer une fonction appartenant à un module - :quiz:`{"type":"TF","answer":"T"}` Une docstring est utile pour documenter le code - :quiz:`{"type":"TF","answer":"T"}` Une docstring peut contenir des doctests - :quiz:`{"type":"TF","answer":"F"}` Un doctest permet de vérifier le comportement d'une fonction pour tous les cas de figure - :quiz:`{"type":"TF","answer":"T"}` Un doctest permet de vérifier le comportement d'une fonction pour un cas de figure - :quiz:`{"type":"TF","answer":"T"}` Un doctest comprend un contexte d'appel et le résultat attendu - Un doctest comprend :quiz:`{"type":"FB","answer":"2", "size":3}` lignes - Pour obtenir un affichage détaillé, on utilise l'option - :quiz:`{"type":"FB","answer":"v", "size":3}`