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.
Prenons l'exemple du programme ci-dessous qui calcule et affiche la moyenne d'un élève sur trois notes:
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 :
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 :
Par exemple pour un élève on écrira.
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*
#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é :
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 :
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.
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.
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.
def mafonction(liste de paramètres):
#bloc d'instructions (optionnel)
return valeur
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.
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()
, ... ;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
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.
def test_0():
v = 5
return a * 2
print(v) # La variable locale v est inconnue à l'extérieur de la fonction
m = 2
def test_1():
return m + 1 # on a accès à la variable globale dans la fonction
test_1()
def test_2():
m = m + 8 # on ne peut modifier la variable globale m dans la fonction
return m
test_2()
def test_3():
global m # cette déclaration permet de modifier la variable globale dans cette fonction
m = m + 8
return m
test_3()
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 :
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 :
variable_1 : float, variable_2 : int
) ;-> float
) ;Ces annotations sont toutefois facultatives, elles peuvent être vérifiées avec le module
MyPy
et importées avec le module typing
Par exemple voici une proposition de documentation, d'assertions et d'annotations de typage statique, pour notre fonction affiche_moyenne()
:
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}."
help(affiche_moyenne)
affiche_moyenne("Boris", 9,7.5,12)
affiche_moyenne(907, 13,10,16)
affiche_moyenne("Anne", -1,10,16)
affiche_moyenne("Anne", 21,10,16)
print(affiche_moyenne("Anne", 20,10,16))
# Faire d'autres tests ici...
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.
def bonjour():
print("Demat! Mont a ra ?")
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.
bonjour()
type(bonjour())
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
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.
def bonjour(nom):
print(f"Demat {nom}")
def compteur(stop):
for i in range(stop):
print(i)
Pour tester ces fonctions, il faut les appeler avec un argument.
bonjour("toto")
compteur(4)
On peut bien sur avoir des fonctions qui appellent des fonctions
def trespoli(nbfois):
for i in range(nbfois):
bonjour("toto")
trespoli(3)
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 :
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.
def compteur(start, stop, step):
for i in range(start, stop, step):
print(i)
compteur(1, 7, 2)
À retenir:
Le premier argument sera affecté au premier paramètre, le second argument sera affecté au second paramètre, et ainsi de suite.
def politesse(nom, titre ="Monsieur"):
print(f"Veuillez agréer, {titre} {nom}, mes salutations distinguées.")
politesse("Dupont")
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.
def compteur_complet(start, stop, step):
for i in range(start, stop, step):
print(i)
compteur_complet(1, 7)
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.
def compteur_autre(start, stop, step):
for i in range(start, stop, step):
print(i)
compteur_autre( step=2, stop=4,start=1)
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:
def f(x):
return 2*x+3
print(f(0))
print(f(1))
type(f(0))
On encore pour améliorer la lisibilité.
for i in range(0,5):
print(f"f({i}) = {f(i)}")
*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
# Votre code ici
*b.* Créer une fonction tri3d(a,b,c)
qui retourne les trois valeurs triées par ordre décroissant
# 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
# Votre code ici
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 :
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 :
math.sqrt(25)
La fonction help()
affiche alors la docstring de cette fonction :
help(math.sqrt)
On peut aussi n'importer qu'une fonction d'un module et même lui attribuer un nom personalisé (un alias) :
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.
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.
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 :
import mon_module
mon_module.affiche_moyenne("Toto", 20, 17, 20)
help(mon_module)
from mon_module import affiche_moyenne as moy
moy("Titi", 12, 9, 15)
moy? # affiche l'aide de la fonction
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
...
Contenus | Capacités attendues | Commentaires |
---|---|---|
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 programmes | Utiliser 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èques | Utiliser la documentation d’une bibliothèque. | Aucune connaissance exhaustive d’une bibliothèque particulière n’est exigible. |
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