#!/usr/bin/env python # coding: utf-8 # # Fonctions - Un BN pour s'initier à la modularisation # > On a déjà utilisé certaines **fonctions** préprogrammées, comme **print()**, **input()**, certaines sont regroupées dans des *modules*... # > # > On peut également en écrire de nouvelles, personnalisées... # > # > L'écriture de fonctions facilite la compréhension, la vérification, le développement, l'exploitation, la modification et la maintenance d'un programme. # # # ## Pourquoi écrire des fonctions : # # Prenons l'exemple du programme ci-dessous qui calcule et affiche la moyenne d'un élève sur trois notes: # In[ ]: prenom1="Anne" note1=13 note2=10 note3=16 moyenneA=(note1+note2+note3)/3 print(f"La moyenne de {prenom1} est: {moyenneA}") # Ainsi, on doit réecrire le programme pour chaque élève de la classe : # In[ ]: prenom2="Boris" note4=9 note5=7.5 note6=12 moyenneB=(note4+note5+note6)/3 print(f"La moyenne de {prenom2} est {moyenneB}") prenom3="Céline" note7=8 note8=12 note9=16 moyenneC=(note7+note8+note9)/3 print(f"La moyenne de {prenom3} est {moyenneC}") prenom4="Denis" note10=11 note11=14 note12=17 moyenneD=(note10+note11+note12)/3 print(f"La moyenne de {prenom4} est {moyenneD}") # Imaginez maintenant le code pour la classe avec **35 élèves!!!!** # # Que se passe-t-il si on veux ajouter une note supplémentaire ? On doit : # - ajouter une note à l'élève ; # - changer le calcul de la moyenne en ajoutant la note supplémentaire et en divisant par 4 ; # - Et ensuite, on doit refaire cela pour tous les élèves. # # Par exemple pour un élève on écrira. # In[ ]: prenom1="Anne" note1=13 note2=10 note3=16 note4=14 moyenneA=(note1+note2+note3+note4)/4 print(f"La moyenne de {prenom1} est {moyenneA}") # C'est ***Fastidieux !*** # # Quand une tâche peut être effectuée plusieurs fois dans un programme, il devient vite TRÈS INTÉRESSANT d'utiliser un morceau de programme qu'on va réutiliser : on isole alors ce morceau de code dans un ***BLOC*** qu'on appelle ***FONCTION*** # In[ ]: #bloc fonction qui calcule la moyenne def calcule_moyenne(note1,note2,note3): moyenne=(note1+note2+note3)/3 return moyenne #appels répétés de la fonction dans le programme principal print(calcule_moyenne(13,10,16)) print(calcule_moyenne(9,7.5,12)) print(calcule_moyenne(8,12,16)) print(calcule_moyenne(11,14,17)) # Ce qu'on a gagné : # # - On a écrit une seule fois le calcul de la moyenne ; # - Si on ajoute un élève, en une ligne de code, on appelle la fonction une fois de plus ; # - Si on ajoute une note, on ne change que le contenu de la fonction calcule_moyenne() ; # - Si on veut ajouter l'affichage d'un message, on peut le faire dans la fonction sans changer le programme principal... # # Comparez maintenant ce dernier programme avec le premier : # avec moins de lignes de programme, on fait plus de choses, et en plus ça reste très lisible ! # # C'est aussi très utile si la personne qui définit la fonction est différente de celle qui l'utilise on peut ainsi utiliser une fonction que l'on n'a pas codé sois même, simplement, on l'appelle ! Comme par exemple on le fait avec la fonction `print()`... # De plus, on peut très facilement modifier la fonction `calcule_moyenne()` pour calculer et renvoyer la moyenne dans un texte plus significatif : # In[ ]: def affiche_moyenne(prenom,note1,note2,note3): moyenne=(note1+note2+note3)/3 return f"La moyenne de {prenom} est {moyenne}." print(affiche_moyenne("Anne", 13,10,16)) print(affiche_moyenne("Boris", 9,7.5,12)) print(affiche_moyenne("Céline", 8,12,16)) print(affiche_moyenne("Denis", 11,14,17)) # On peut ainsi utiliser ces fonctions autant de fois et quand on le souhaite sans avoir à les réécrire. Elles permettent de rendre le code du programme plus court et plus facile à comprendre. # # ## Définition d'une fonction : # # Une fonction encapsule une portion de programme que l'on souhaite réutiliser. Il nous faut lui attribuer un nom significatif pour pouvoir l'appeller ensuite... # # Une fonction peut-être vu comme une boite noire qui fait quelque chose. On attribue (passe) des valeurs en arguments à ses paramètres d'entrée pour récupèrer un résultat à la sortie. # # Fonction logique traiter # # > Idéalement une fonction devrait se limiter au traitement d'une chose. S'il y a une autre chose à faire il est préférable de la traiter dans une autre fonction dédiée. # # # ### La syntaxe python est # ```python # def mafonction(liste de paramètres): # #bloc d'instructions (optionnel) # return valeur # ``` # - La ligne contenant `def` se termine obligatoirement par un deux-points `:`, qui introduisent un bloc d'instructions qui est précisé grâce à l'indentation. # > Ne pas oublier les deux points à la fin et faire attention à l'indentation, ce sont des erreurs fréquentes au début. # - Ce bloc d'instructions constitue le corps de la fonction ; # - Il est possible d'utiliser `print()` dans une fonction mais il est préférable d'utiliser `return` (pour retourner) et d'appeler la fonction dans la fonction `print()` ou une autre fonction d'affichage comme `HTML()`, `markdown()`, ... ; # - Vous pouvez choisir le nom de votre choix pour la fonction que vous créez, sans utiliser de caractère spécial ou accentué, le caractère souligné « \_ » est autorisé et remplace l'espace ; sauf les mots-clés tels que : # > Remarque : Ne pas utiliser comme nom de variables, ni comme nom de fonctions, les mots clés : # > # > and ; as ; assert ; break ; class ; continue ; def ; del ; elif ; else ; except ; False ; finally ; for ; from ; global ; if ; import ; in ; is ; lambda ; None ; nonlocal ; not ; or ; pass ; raise ; return ; True ; try ; while ; with ; yield # # - On utilise par convention des minuscules, notamment au début du nom. # ## Variables locales et variables globales # Lorsque des variables sont définies à l'intérieur du corps d'une fonction, ces variables ne sont accessibles qu'à la fonction elle-même : ce sont des **variables locales** ; # # Lorsque des variables sont définies à l'extérieur d'une fonction : ce sont des **variables globales**. Leur contenu est visible à l'intérieur d'une fonction, mais la fonction ne peut pas les modifier. # # On dit que les **variables locales** et les **variables globales** n'ont pas la même **portée**. # # > D'une manière générale et en particulier lorsqu'on écrit des fonctions, il est recommandé d'éviter d'utiliser des variables globales. #

A faire vous-même - Tester les exemples suivants :

# In[ ]: def test_0(): v = 5 return a * 2 print(v) # La variable locale v est inconnue à l'extérieur de la fonction # In[ ]: m = 2 def test_1(): return m + 1 # on a accès à la variable globale dans la fonction test_1() # In[ ]: def test_2(): m = m + 8 # on ne peut modifier la variable globale m dans la fonction return m test_2() # In[ ]: def test_3(): global m # cette déclaration permet de modifier la variable globale dans cette fonction m = m + 8 return m test_3() # ## Documentation, spécification d'une fonction : # # Pour faciliter l'utilisation d'une fonction par autrui (ou soi même après quelques semaines...) il est nécessaire qu'elle soit bien documentée. # # Cette spécification s'écrit sur plusieures lignes dans une "Docstring", avec 3 guillemets ouvrants et 3 guillemets fermants : # # ```python # def ma_fonction(liste de paramètres): # """ # Description : # # Exemple : # # Préconditions : # # Postconditions : # # """ # # Assertions de vérification des préconditions : # # assert condition , 'message' # # # bloc d'instructions : # # return # ``` # Il s'agit de décrire ce que fait la fonction et comment s'en servir. # # On y précise pour cela les préconditions sur les paramètres d'entrée (type, attendues et limitations quant aux valeurs, ...) et les postconditions sur les sorties (type, description de la valeur renvoyée). # # On peut également ajouter un exemple d'appel de la fonction et le résultat qu'elle produit alors. # # De plus, on doit fournir des tests qui permettent de vérifier son fonctionnement. Il est possible de les indiquer dans la docstring mais on peut aussi les écrire dans le code sous la forme d'assertions pour vérifier les préconditions et ainsi augmenter la robustesse de notre fonction. # # Enfin, la tendance étant aux langages de programmation fonctionnelles qui utilisent un typage statique, l'utilisation des annotations de type se généralise depuis la version 3.5 de Python : # - On peut préciser les types des paramètres d'entrée (``variable_1 : float, variable_2 : int``) ; # - Et le type du retour (``-> float``) ; # > Ces annotations sont toutefois facultatives, elles peuvent être vérifiées avec le module [`MyPy`](https://blog.invivoo.com/type-checking-in-python-avec-mypy/) et importées avec le module [typing](https://docs.python.org/3/library/typing.html) # # Par exemple voici une proposition de documentation, d'assertions et d'annotations de typage statique, pour notre fonction ``affiche_moyenne()`` : # In[ ]: def affiche_moyenne(prenom : str, note1 : float, note2 : float, note3 : float) -> str : """ ================================================================================================================== * Description : Je calcule la moyenne de 3 notes sur 20 d'un élève et renvoie le résultat formaté pour un affichage de texte ; * Exemple : >>> affiche_moyenne("Boris", 9,7.5,12) 'La moyenne de Boris est 9.5.' * Préconditions : - prenom (str) : une chaine de caractères identifiant de l'élève ; - note_ (float) : un nombre entier ou flottant compris entre 0 et 20 inclus ; * Postconditions : (str) : une chaine de caractère formatée contenant l'identifiant de l'élève et sa moyenne calculée. ================================================================================================================== """ # Assertions de vérification des préconditions : assert type(prenom) == str , "La valeur du premier argument doit être une chaine de caractères identifiant l'élève" assert note1 >= 0.0 , "Le second argument est une note comprise entre 0 et 20 inclus" assert note1 <= 20.0 , "Le second argument est une note comprise entre 0 et 20 inclus" # bloc d'instructions : moyenne=(note1+note2+note3)/3 return f"La moyenne de {prenom} est {moyenne}." #

A faire vous-même - Tester dans les cellules suivantes la fonction affiche_moyenne() et vérifier sa robustesse :

# In[ ]: help(affiche_moyenne) # In[ ]: affiche_moyenne("Boris", 9,7.5,12) # In[ ]: affiche_moyenne(907, 13,10,16) # In[ ]: affiche_moyenne("Anne", -1,10,16) # In[ ]: affiche_moyenne("Anne", 21,10,16) # In[ ]: print(affiche_moyenne("Anne", 20,10,16)) # In[ ]: # Faire d'autres tests ici... # ## Quelques exemples de fonctions très simples : # # En programmation informatique on rencontre une grande variété de fonctions ayant un ou plusieurs paramètres, voire aucun. De plus les paramètres en entrée d'une fonction peuvent être de types différents. # # De même les fonctions peuvent retourner une chaîne de caractères, un nombre, un tuple, ..., voire même ne rien retourner du tout. # # ### Fonctions sans paramètre : # # #### Exemples : # In[ ]: def bonjour(): print("Demat! Mont a ra ?") # In[ ]: def compteur(): for i in range(4): print(i) # Nous avons défini une première fonction qui affiche le texte ***Bonjour*** et une deuxième fonction simple qui affiche les entier de 0 à 3. # Notez bien les parenthèses, les deux-points, et l'indentation du bloc d'instructions qui suit la ligne d'en-tête (c'est ce bloc d'instructions qui constitue le corps de la fonction proprement dite). # # Avoir une fonction, c'est bien mais encore faut il l'utiliser. # In[ ]: bonjour() # In[ ]: type(bonjour()) # In[ ]: compteur() # Nous pouvons maintenant réutiliser cette fonction à plusieurs reprises, autant # de fois que nous le souhaitons. # # Nous pouvons également l'incorporer dans la définition d'une autre fonction. # # **Exemple de fonction qui appelle une autre fonction** # In[ ]: def poli(): for i in range(3): bonjour() poli() # Une première fonction peut donc appeler une deuxième fonction, qui elle-même en appelle une troisième, etc. # # Créer une nouvelle fonction offre l'opportunité de donner un nom à tout un ensemble d'instructions. De cette manière, on peut simplifier le corps principal d'un programme, en dissimulant un algorithme secondaire complexe sous une commande unique, à laquelle on peut donner un nom explicite. # # Une fonction est donc en quelque sorte une nouvelle instruction personnalisée, qu'il est possible d'ajouter librement à notre langage de programmation. # # ### Fonction avec paramètres. # # #### Exemples : # In[ ]: def bonjour(nom): print(f"Demat {nom}") # In[ ]: def compteur(stop): for i in range(stop): print(i) # Pour tester ces fonctions, il faut les appeler avec un argument. # In[ ]: bonjour("toto") # In[ ]: compteur(4) # On peut bien sur avoir des fonctions qui appellent des fonctions # In[ ]: def trespoli(nbfois): for i in range(nbfois): bonjour("toto") trespoli(3) # ### Utilisation d'une variable comme argument. # # L'argument que nous utilisons dans l'appel d'une fonction peut être une variable. # # #### Exemple : # In[ ]: a = "Alan Turing" bonjour(a) # Dans l'exemple ci-dessus, l'argument que nous passons à la fonction `bonjour()` est le contenu de la variable `a`. # À l'intérieur de la fonction, cet argument est affecté au paramètre `stop`, qui est une tout autre variable. # # Notez donc bien dès à présent que : # # * Le nom d'une variable que nous passons comme argument n'a rien à voir avec le nom du paramètre correspondant dans la fonction. # * Ces noms peuvent être identiques si vous le voulez, mais vous devez bien comprendre qu'ils ne désignent pas la même chose (en dépit du fait qu'ils puissent contenir une valeur identique). # # ### Fonction avec plusieurs paramètres. # # #### Exemples : # In[ ]: def bonjour(prenom, nom): print(f"Demat {prenom} {nom}") bonjour("Alan","Turing") bonjour("Ada","Lovelace") # La fonction suivante utilise trois paramètres : `start` qui contient la valeur de départ, `stop` la borne supérieure exclue comme dans l'exemple précédent et `step` le pas du compteur. # In[ ]: def compteur(start, stop, step): for i in range(start, stop, step): print(i) compteur(1, 7, 2) # À retenir: # # - Pour définir une **fonction avec plusieurs paramètres**, il suffit d'inclure **les paramètres** entre les parenthèses qui suivent le nom de la fonction, en **les séparant à l'aide de virgules**. # - Lors de l'appel de la fonction, **les arguments utilisés** doivent être fournis dans **le même ordre** que celui des paramètres correspondants (en les séparant eux aussi à l'aide de virgules). # Le premier argument sera affecté au premier paramètre, le second argument sera affecté au second paramètre, et ainsi de suite. # In[ ]: def politesse(nom, titre ="Monsieur"): print(f"Veuillez agréer, {titre} {nom}, mes salutations distinguées.") politesse("Dupont") # ### Valeurs par défaut pour les paramètres # # Dans la définition d'une fonction, il est possible de définir un argument par # défaut pour chacun des paramètres. On obtient ainsi une fonction qui peut être # appelée avec une partie seulement des arguments attendus. # # #### Exemples : # In[ ]: def compteur(start, stop, step=1): for i in range(start, stop, step): print(i) compteur(1, 4) # Lorsque l'on appelle cette fonction en ne lui fournissant que les deux premier arguments, le troisième argument step second reçoit tout de même une valeur par défaut (ici 1). # # alors que sans ça, l'oublie d'un paramètre amène à une erreur comme ci-dessous. # In[ ]: def compteur_complet(start, stop, step): for i in range(start, stop, step): print(i) compteur_complet(1, 7) # ### Arguments avec étiquettes # # Dans la plupart des langages de programmation, les arguments que l'on fournit lors de l'appel d'une fonction doivent être fournis exactement dans le même ordre que celui des paramètres qui leur correspondent dans la définition de la # fonction. # # Python autorise l'appel aux fonctions en fournissant les arguments correspondants **dans n'importe quel # ordre, à la condition de désigner nommément les paramètres correspondants**. # # #### Exemple : # In[ ]: def compteur_autre(start, stop, step): for i in range(start, stop, step): print(i) compteur_autre( step=2, stop=4,start=1) # ## Les fonctions avec return # # # Vous avez vu en mathématiques les fonctions pour un $x$ donnée **retournent** une valeur $f(x)$ (éventuellement). # # python utilise le mot clé ***return*** # # Par exemple pour la fonction $f: x \longmapsto 2x+3$ on utilisera la fonction: # In[ ]: def f(x): return 2*x+3 print(f(0)) print(f(1)) # In[ ]: type(f(0)) # On encore pour améliorer la lisibilité. # In[ ]: for i in range(0,5): print(f"f({i}) = {f(i)}") #

A coder vous-même - Exercice :

# # ***a.*** Créer une fonction `tri3c(a,b,c)` qui retourne les trois valeurs triées par ordre croissant # # Voici un algorithme possible en pseudo code. # # __Algorithme : Tri de 3 valeurs__ # __entrée__ : les réels $a, b$ et $c$. # __sortie__ : Les réels $a, b$ et $c$ trié par ordre croissant. # # >Fonction tri3c(a,b,c) # >> __si__ a > b alors # >>> on échange a et b # >> # >> __si__ b > c alors # >>> on échange b et c # >> # >> __si__ a > b alors # >>> on échange a et b # # __Fin__ # In[ ]: # Votre code ici # ***b.*** Créer une fonction `tri3d(a,b,c)` qui retourne les trois valeurs triées par ordre décroissant # In[ ]: # Votre code ici # ***c.*** Créer une fonction `tri3(a,b,c,reverse=False)` qui retourne les valeurs $a, b$ et $c$ triées par ordre croissant et décroissant si `reverse=True` # In[ ]: # Votre code ici # ## Modules : # # ### Importer les fonctions d'un module : # # Un module est un fichier qui rassemble le code de fonctions et des variables qui ont généralement un rapport entre elles. # # Il existe différentes méthodes pour importer un module afin d'utiliser ses fonctions et ses variables : # In[ ]: import math # Toutes les fonctions contenues dans ce module sont maintenant accessibles dans ce carnet. # # La fonction ``help()`` permet alors d'accéder à la documentation de ce module : # In[ ]: help(math) # Pour appeler une fonction du module, il faut taper le nom du module suivi d'un point « . » puis du nom de la fonction : # In[ ]: math.sqrt(25) # La fonction ``help()`` affiche alors la docstring de cette fonction : # In[ ]: help(math.sqrt) # On peut aussi n'importer qu'une fonction d'un module et même lui attribuer un nom personalisé (un alias) : # In[ ]: from math import sqrt as rc rc(25) # > D'une manière générale, et surtout lorsqu'on importe plusieurs modules différents dans le même programme, il faut éviter de faire `from math import *`pour ne pas risquer d'avoir des conflits de nom de fonction. # # ### Créer un module personalisé : # # Dans le dossier qui contient ce carnet jupyter, créer un nouveau fichier texte et le nommer ``mon_module.py``. # # Copier puis coller le code suivant de la fonction `affiche_moyenne()` dans ce fichier et l'enregistrer. # # ````Python # def affiche_moyenne(prenom : str, note1 : float, note2 : float, note3 : float) -> str : # """ # ================================================================================================================== # # * Description : # Je calcule la moyenne de 3 notes sur 20 d'un élève et renvoie le résultat formaté pour un affichage de texte ; # # * Exemple : # >>> affiche_moyenne("Boris", 9,7.5,12) # 'La moyenne de Boris est 9.5.' # # * Préconditions : # - prenom (str) : une chaine de caractères identifiant de l'élève ; # - note_ (float) : un nombre entier ou flottant compris entre 0 et 20 inclus ; # # * Postconditions : # (str) : une chaine de caractère formatée contenant l'identifiant de l'élève et sa moyenne calculée. # # ================================================================================================================== # """ # # Assertions de vérification des préconditions : # assert type(prenom) == str , "La valeur du premier argument doit être une chaine de caractères identifiant l'élève" # assert note1 >= 0.0 , "Le second argument est une note comprise entre 0 et 20 inclus" # assert note1 <= 20.0 , "Le second argument est une note comprise entre 0 et 20 inclus" # # # # bloc d'instructions : # moyenne=(note1+note2+note3)/3 # return f"La moyenne de {prenom} est {moyenne}." # ```` # # Dans ce carnet, exécuter alors les cellules suivantes : # In[ ]: import mon_module # In[ ]: mon_module.affiche_moyenne("Toto", 20, 17, 20) # In[ ]: help(mon_module) # In[ ]: from mon_module import affiche_moyenne as moy moy("Titi", 12, 9, 15) # In[ ]: moy? # affiche l'aide de la fonction # In[ ]: moy?? ## affiche le code source de la fonction # Toutes les fonctions qui sont contenues dans le fichier ``mon_module.py`` seront accessibles après `import` dans tout carnet jupyter ouvert depuis le dossier du fichier ``mon_module.py``... # **** # ## Références aux programmes : # # # # # # # # # # # # # # # # # # # # # # # #
ContenusCapacités attenduesCommentaires
Spécification.Prototyper une fonction.
Décrire les préconditions sur les arguments.
Décrire des postconditions sur les résultats.
Des assertions peuvent être utilisées pour garantir des préconditions ou des postconditions.
Mise au point de programmesUtiliser des jeux de tests.L’importance de la qualité et du nombre des tests est mise en évidence.
Le succès d’un jeu de tests ne garantit pas la correction d’un programme.
Utilisation de bibliothèquesUtiliser la documentation d’une bibliothèque.Aucune connaissance exhaustive d’une bibliothèque particulière n’est exigible.
# Licence Creative Commons
Ce document est basé sur un travail partagé par Jean-Claude MEILLAND, il est mis à disposition selon les termes de la Licence Creative Commons Attribution - Partage dans les Mêmes Conditions 4.0 International. # # Pour toute question, suggestion ou commentaire : eric.madec@ecmorlaix.fr