Commençons par revoir ce que vous savez déjà sur les fonctions.
Rappels
(Rappel) 1.1 FONCTION : notion
ENTREE(S)
⇒
Fonction
⇒
SORTIE
Description
Les fonctions sont des instructions particulières qui :
reçoivent des données d'entrée et
renvoient leur réponse en sortie.
Un exemple avec la fonction VALEUR_ABSOLUE qui n'attend qu'une entrée (un nombre) et qui renvoie la valeur absolue du nombre (le même nombre mais sans son signe).
-12
⇒
Fonction VALEUR ABSOLUE
⇒
12
12
⇒
Fonction VALEUR ABSOLUE
⇒
12
D'autres exemples
5, 10, 50, 0, 20
⇒
Fonction MAXIMUM
⇒
50
5, 10, 50, 0, 20
⇒
Fonction MINIMUM
⇒
0
5, 10, 50, 0, 20
⇒
Fonction SOMME
⇒
85
(Rappel) 1.2 - FONCTION : native
Une fonction native est une fonction présente de base dans Python.
On en fait l'appel en notant son nom et en fournissant entre parenthèses les entrées séparées par des virgules.
nom_fonction(a, b, c, d...)
Dans le cadre de ce site, les fonctions natives sont toujours écrites en vert.
Fonction VALEUR ABSOLUE : cette fonction se nomme abs() en Python.
>>> abs(-12)12>>> abs(12)12
Fonction MAXIMUM : cette fonction se nomme max() en Python.
La déclaration (mise en mémoire d'instructions pour les utiliser plus tard)
L'appel (utilisation de ces instructions)
A - Déclarer une fonction
Déclarer la fonction revient à stocker ses instructions.
Pour déclarer une fonction, on écrit dans l'ordre
le mot-clé def.
le nom de la fonction
entre parenthèses : les variables d'entrée séparées par des virgules
le caractère :
les instructions du bloc tabulé
1
2
defdouble(x):return2*x
Important : lors de la déclaration, on ne lance aucun appel, on ne fait que mettre en mémoire.
Le mot-clé return permet d'indiquer ce que la fonction devra renvoyer si on l'appelle.
Placer des commentaires en français dans le code Python
On peut placer du texte à destination des développeurs qui vont lire le programme en utilisant le caractère #. Voir la partie suivante pour un exemple.
Attention : sur votre ligne, tout ce qui se trouve derrière ce # sera purement ignoré par l'interpréteur.
B - Lancer un appel de fonction
Lancer un appel revient à fournir les entrées et attendre la réponse de la fonction.
Ligne 1 : déclaration de nouvelle_note() qui va recevoir une donnée qu'on placera dans note.
Ligne 7 (appel à la fonction nouvelle_note() en lui envoyant 12).
Ligne 1 à 5 : on exécute les lignes avec note contenant 12 initialement. Après la ligne 2, note contient donc 17. Le test de la condition étant alors faux (17 n'est pas supérieur à 20), on passe en ligne 5 : la fonction envoie 17 en sortie.
L7 (réception) : on stocke 17 dans n.
Ne zappez pas les rappels si vous ne les avez jamais vu. Sinon, le reste de l'activité risque de vous sembler difficile...
Quelques compléments
1.5 - FONCTION : une déclaration pas à pas
1
2
3
defma_fonction(x):resultat=x * 2returnresultat
Voici comment déclarer une fonction :
On commence la déclaration par le mot-clé def suivi du nom de la fonction. Comme pour les variables, le nom de la fonction doit être explicite : son nom doit permettre de comprendre ce qu'elle réalise.
1
deffois2
on rajoute des parenthèses,
1
deffois2()
entre les parenthèses, on fournit les noms des variables qui vont servir à mémoriser les ENTREES que la fonction va recevoir dans le futur (ici, c'est x).
1
deffois2(x)
On finit cette première ligne par un POINT DOUBLE : signifiant qu'on a fini de fournir le nom et les paramètres d'entrée.
1
deffois2(x):
Les instructions à réaliser par la fonction sont toutes indentées (4 espaces ou touche TAB). C'est comme cela que l'interpréteur Python qu'une instruction appartient à la fonction (ou pas).
1
2
deffois2(x):resultat=x * 2
On finit par return suivi de la réponse qu'on veut renvoyer, ici resultat
1
2
3
deffois2(x):resultat=x * 2returnresultat
J'insiste mais... "Déclarer" n'est pas "Faire appel"
Il faut comprendre qu'après avoir exécuté ces lignes, l'interpréteur Python ne lance pas d'appel à votre fonction. D'ailleurs, il aurait du mal... Que mettrait-il dans x de toutes manières ?
Déclarer la fonction permet juste de mettre ce nom en mémoire pour pouvoir y faire appel plus loin dans le programme.
1.6 return : on peut utiliser directement une expression
Lorsque l'interpréteur rencontre le mot-clé return, il réalise deux choses :
Il évalue l'expression située derrière return.
Il renvoie la valeur trouvée à l'endroit d'où a été lancé l'appel initialement.
Deux choix de conception :
Stocker d'abord le résultat dans une variable, puis placer ce nom de variable derrière return.
1
2
3
deffois2(x):resultat=x * 2returnresultat
Placer directement l'expression derrière return.
1
2
deffois2(x):returnx * 2
Ici, il est donc inutile de stocker la réponse dans une variable puisque la seule instruction derrière serait de renvoyer le résultat stocké.
return n'est pas une fonction !
Ne placez pas de parenthèses (sauf obligation) derrière le return. Sinon, vous laissez croire qu'il s'agit d'une fonction. Il s'agit bien d'un mot-clé qui renvoie la réponse à celui qui a posé la question.
Rajoutons maintenant une couche de vocabulaire à connaître.
1.7 FONCTION : paramètre et argument
1
2
3
defvaleur(d, u):# d et u sont les paramètres déclarésresultat=d*10 + ureturnresultat
>>> valeur(5, 3) # 5 et 3 sont les Arguments d'Appel
53
Différence entre paramètre et argument
Il faut savoir distinguer
La déclaration qui indique les variables locales permettant de stocker les entrées qu'on fournira un jour.
Plutôt que de dire les variables locales de stockage d'entrées qu'on fournira un jour", on dira les paramètres.
1
2
3
defvaleur(d, u):# d et u sont les paramètres déclarésresultat=d*10 + ureturnresultat
Ces paramètres disparaissent donc une fois que la fonction a répondu.
L'appel qui fournit les entrées envoyées à la fonction sur cet appel. Plutôt que de dire les "entrées envoyées à la fonction sur cet appel", on utilisera le mot argument.
>>> valeur(5, 3) # 5 et 3 sont les Arguments d'Appel
53
L'argument n°1 (5) va dans le paramètre n°1 (d), ect...
Cas d'une fonction avec 1 paramètre
1
deffois2(x):<-- x est le paramètre de déclaration
>>> fois2(20) <-- 20 est l'argument de l'appel40
Lors de l'appel, on envoie l'argument 20.
L'argument 20 est alors stocké dans le paramètre x
Cas d'une fonction avec 2 paramètres
1
defvaleur(d, u):<-- d et u sont les paramètres
>>> valeur(5, 3) <-- 5 et 3 sont les arguments de l'appel
Lors de l'appel, on envoie l'argument 5 suivi de l'argument 3.
L'argument 5 est donc stocké dans d et l'argument 3 dans le paramètre u
Moyen mnémotechnique pour les interros
appel (commence par a) : argument (commence par a)
déclaration (commence par d) : paramètre (commence par p, un d à l'envers)
Précision
L'appel peut bien entendu se faire directement dans le programme. C'est même beaucoup plus courant.
1
2
3
4
5
defvaleur(d, u):# d et u sont les paramètres déclarésresultat=d*10 + ureturnresultatvaleur(5, 3) <-- 5 et 3 sont les arguments de l'appel
Paramètre formel et effectif
Il existe une deuxième manière de nommer les choses.
Paramètre formel plutôt que paramètre.
Paramètre effectif plutôt qu'argument.
Beaucoup de questions risquent de vous paraître un peu bête si vous avez vraiment compris le mécanisme des fonctions. Le but ici est bien de casser les mauvaises conceptions mentales, quitte à ouvrir des portes ouvertes.
01° Réaliser les 4 actions suivantes
Ouvrir l'onglet VARIABLES de Thonny pour vérifier que la mémoire est vide initialement
Sauvegarder et lancer le programme suivant sous le nom activite_fonctions.
Vérifier via l'onglet VARIABLES que la mémoire contient maintenant deux nouvelles variables nommées fois2 et plus2.
Le at 0x de l'onglet "Variables" ... fait référence à une zone mémoire de stockage et cela devrait donc être différent sur votre machine. Par contre, vous devriez avoir quelque chose de proche à l'écran :
Utiliser ensuite la console pour visualiser que fois2 (juste le nom, sans les parenthèses) ne lance aucun appel mais spécifie que cette variable est de type function : elle référence des instructions.
>>> fois2
<function fois2 at 0x7f2d96628af0>>>> type(fois2)
<class 'function'>
Vous allez maintenant pouvoir faire appel à vos fonctions. Réaliser les appels suivants dans la console de Python, observer les réponses et puis répondre aux questions :
>>> plus2(10)
???
Quel est l'argument envoyé à la fonction plus2() lors de cet appel ?
Quel est le contenu du paramètre x lors de cet appel ?
Quelle est la réponse de cet appel (qui s'affiche dans la console interactive) ?
>>> plus2(15)
???
Quel est l'argument envoyé à la fonction plus2() lors de cet appel ?
Quel est alors le contenu affecté au paramètre x lors de cet appel ?
Quelle est la réponse de cet appel ?
>>> fois2(10)
???
Quel est l'argument envoyé à la fonction fois2() lors de cet appel ?
Quel est alors le contenu affecté au paramètre x lors de cet appel ?
Quelle est la réponse de cet appel ?
...CORRECTION...
>>> plus2(10)
12
Ici, on voit que l'entrée est 10.
Le paramètre x référence l'entier 10.
Cet appel calcule 10+2 et renvoie 12.
>>> plus2(15)
17
Cette fois, on envoie 15.
x référence 15.
Cet appel calcule 15+2 et renvoie 17.
>>> fois2(10)
20
On envoie une entrée valant 10.
x référence 10.
Cet appel calcule 2*10 et renvoie donc 20.
03° Un peu plus complexe que la précédente. Réaliser les appels suivants dans la console de Python, observer les réponses et puis répondre aux questions :
>>> a = plus2(100)
Quel est l'argument de cet appel ?
Quel est le contenu de x lors de cet appel ?
Quelle est la réponse de cet appel ?
Pourquoi n'obtient-on pas d'affichage dans la console ?
>>> b = fois2(a)
Quel est l'argument de cet appel ?
Quel est le contenu du paramètre x lors de cet appel ?
Quelle est la réponse de cet appel ?
Pourquoi n'obtient-on pas d'affichage dans la console ?
>>> c = fois2(plus2(10))
Que va évaluer d'abord l'interpréteur Python à cause des priorités ?
Quel est l'argument envoyé à la fonction fois2() ?
Expliquer le contenu de c.
...CORRECTION...
>>> a = plus2(100)
L'argument envoyé est 100.
x contient donc 100.
La fonction répond donc 102 qu'on affecte à la variable a.
Ici, il y a une affectation dans une variable. La console n'affiche un résultat que si la ligne ne contient que l'évaluation.
>>> b = fois2(a)
L'argument envoyé est a.
x référence donc 102.
La fonction répond donc 204 qu'on affecte à la variable b.
Ici, il y a une affectation dans une variable. La console n'affiche un résultat que si la ligne ne contient que l'évaluation.
>>> c = fois2(plus2(10))
En respectant la priorité des parenthèses, on se rend compte qu'il faut d'abord évaluer plus2(10).
Python va évaluer plus2(10) à la valeur 12. On envoie donc ce 12 comme argument à fois2().
c récupère donc la valeur évaluée par fois2(12), soit 24.
04° Réaliser les appels suivants qui provoquent tous des erreurs. Pour chaque appel, observer la dernière ligne du message d'erreur de Python et répondre aux questions proposées :
C'est une erreur TypeError. En Python, il s'agit d'une erreur d'exécution.
On envoie 0 argument lors de l'appel alors que la déclaration de la fonction possède UN paramètre. Il manque donc un paramètre.
>>> fois2(10, 20)
TypeError: fois2() takes 1 positional argument but 2 were given
C'est une erreur TypeError. En Python, il s'agit d'une erreur d'exécution.
On envoie 2 arguments lors de l'appel alors que la fonction possède UN paramètre.
>>> fois2 10
SyntaxError: invalid syntax
C'est une erreur de SYNTAXE. Python ne comprend pas l'appel.
L'utilisateur a oublié les parenthèses nécessaires en Python lors de l'appel.
>>> fois2([10])
[10, 10]
Par d'erreur en soi : pas d'erreur de syntaxe, ni d'erreur d'exécution.
Par contre, si l'utilisateur voulait vraiment obtenir un entier multiplié par deux, c'est une erreur de sémantique : il envoie un tableau de 1 case. Python fait son travail, il renvoie un tableau de deux cases identiques.
A l'opérateur * qui possède une signature int * list -> list dont la sémantique est la répétition et pas la multiplication au sens mathématique.
05° Répondre à trois ensembles de questions ci-dessous.
A - AVANT d'avoir exécuter quoi que ce soit
Fournir la succession des lignes suivies par l'interpréteur Python lors de l'exécution de ce programme.
Aller sur Python Tutor, et visualiser le déroulé du programme suivant en mode pas à pas.
C - APRES Python Tutor
Lorsqu'on exécute la ligne 1, met-on la fonction en mémoire ou lance-t-on la fonction ?
Les variables d et u existent-elles dans l'espace des noms global ?
Que devient son espace des noms local après que la fonction ai répondu ?
Les variables a, b et c sont-elles des variables locales ou globales ?
...CORRECTION...
Partie A
L1 (déclaration)
L5 (appel en envoyant 2 et 3) - L1 - L2 - L3(réponse)
L5 (réception puis affectation)
L6 (appel en envoyant 2 et 3) - L1 - L2 - L3(réponse)
L6 (réception puis affectation)
L7 (appel en envoyant 2 et 3) - L1 - L2 - L3(réponse)
L7 (réception puis affectation)
Fin
Partie C
On place juste la fonction en mémoire. C'est une déclaration, pas un appel.
Chaque appel de fonction génère son propre espace des noms local et temporaire lui permettant de stocker les arguments reçus dans ses propres paramètres. d et u sont des variables locales.
Dès qu'une fonction a répondu, l'espace des noms local de cet appel est supprimé puisqu'il ne sert plus à rien.
Les variables a, b et c sont globales puisqu'elles sont déclarées directement dans le programme.
06° Placer la fonction valeur() en mémoire dans Thonny. Utiliser ensuite ceci dans la console :
1
2
3
defvaleur(d, u):resultat=d*10 + ureturnresultat
>>> valeur(2, 6)
26
Question
Expliquer la valeur 26 obtenue sur cet appel en montrant comment on compare l'appel et la déclaration.
...CORRECTION...
1
defvaleur(d, u):
>>> valeur(2, 6)
L'argument 2 est stockée dans le paramètre d.
L'argument 6 est stockée dans le paramètre u.
La fonction calcule donc 2*10 + 6 et renvoie donc 26.
07° Expliquer la réponse qu'on va obtenir lors de l'appel de la fonction facile() puis plus_difficile() ci-dessous. On vous donne leurs déclarations de la fonction et l'appel voulu dans la console interactive.
Première fonction
1
2
deffacile(x):returnx*10+5
>>> facile(3)
???
Deuxième fonction
1
2
defplus_difficile(x, y, z):returnx*100+y*10 + z
>>> plus_difficile(5, 6, 3)
???
...CORRECTION...
Le 3 est stocké dans la variable locale x. La première fonction calcule donc 3*10+5, et renvoie 35.
En comparant appel et déclaration pour la deuxième fonction, on voit que x reçoit 5, y reçoit 6 et z reçoit 3. La fonction calcule 500+60+3 et renvoie 563.
08° Pour valider cette première partie :
Appuyer sur le bouton VISUALISER ci-dessous, pour observer l'ordre d'exécution de ce programme
Expliquer les valeurs finales stockées dans les variables a et b après exécution du code ci-dessous.
Résoudre des problèmes en utilisant des programmes basés sur des algorithmes.
Qu'est-ce qu'un beau programme ?
Point 1 : d'abord des fonctions qui fonctionnent en donnant toujours le bon résultat
On en parlera dans la partie ALGORITHMIQUE.
Point 2 : ensuite des fonctions facilement utilisables
On en parlera un peu aujourd'hui avec la notion de DOCUMENTATION.
Point 3 : ensuite des fonctions dont le code interne est facile à comprendre
On en parlera un peu aujourd'hui avec la notion de COMMENTAIRE.
Point 4 : enfin, des fonctions qui répondent rapidement
On en parlera dans la partie ALGORITHMIQUE.
(Rappel) 2.1 COMMENTAIRES : expliquer le fonctionnement
Les commentaires sont destinés à un lecteur humain et ils visent à rendre le code interne facile à comprendre.
Cela doit permettre de modifier un code même plusieurs années après sa création initiale.
Pour rajouter un commentaire en Python, on utilise le caractère dièse (#) de façon adaptée. Trois exemples à parfaitement comprendre :
Commentaire sur toute une ligne (ligne 1 ci-dessous)
1
# Toute cette ligne est un commentaire.
Commentaire en fin de ligne (ligne 2 ci-dessous)
2
print("Bonjour tout le monde")# Ceci est également un commentaire
Notez bien que la ligne 3 ne comporte aucun commentaire puisque le # fait juste partie d'un string.
3
print("Cette ligne ne contient pas de # commentaire")
Voici le résultat de ce programme L1 à L3 dans la console : il n'affiche pas les commentaires.
1
2
3
# Toute cette ligne est un commentaire.print("Bonjour tout le monde")# Ceci est également un commentaireprint("Cette ligne ne contient pas de # commentaire")
>>> %Run progcommentaires.py
Bonjour tout le monde
Cette ligne ne contient pas de # commentaire>>>
L'interpréteur Python ne tentera pas d'exécuter les commentaires.
Il reste à voir le Point 2 : comment parvenir à créer des fonctions facilement UTILISABLES même si nous n'en sommes pas le créateur.
2.2 DOCUMENTATION : expliquer l'utilisation
2.2.1 Documenter pour utiliser
La documentation doit expliquer comment utiliser la fonction sans provoquer d'erreur.
Si on prend l'analogie de la voiture :
Les commentaires expliquent comment fonctionne la voiture (cours de mécanique, d'électronique...)
La documentation explique comment utiliser la voiture (cours de code et de conduite)
2.2.2 Trouver la documentation
En Python, pour trouver la documentation, il suffit d'utiliser une fonction native : la fonction help().
Imaginons qu'on veuille récupérer la documentation de la fonction native len() : il faut donner le nom de la fonction en tant qu'argument à la fonction type(). Attention, juste le nom, sans les parenthèses. On ne veut pas l'activer.
>>> help(len)
Help on built-in function len in module builtins:
len(obj, /)
Return the number of items in a container.
Première ligne : on voit que len() est une fonction native (build-in).
Help on built-in function len in module builtins:
Deuxième ligne : à qui on doit fournir un seul argument (qui sera rangé dans un paramètre obj visiblement).
len(obj, /)
Troisième ligne : on voit que la fonction va renvoyer le nombre d'éléments contenus dans le conteneur envoyé.
Return the number of items in a container.
2.2.3 Contenu d'une documentation
La documentation contient juste des informations sur
Le but de cette fonction
le type et le nombre d'arguments à lui envoyer (les ENTREES) et
le type et le contenu de la réponse (la SORTIE).
09° Taper ceci dans la console pour récupérer la documentation (écrite par le concepteur de Python) de la fonction bin().
>>> help(bin)
Help on built-in function bin in module builtins:
bin(number, /)
Return the binary representation of an integer.
>>> bin(2796202)
'0b1010101010101010101010'
Nous verrons bientôt à quoi correspond le / dans la documentation. Pour l'instant, faisons comme s'il n'était pas là.
Questions
Dans la documentation, où est-il indiqué que bin() est une fonction native ?
combien d'argument(s) doit-on envoyer à bin() ? De quel(s) type(s) ?
En traduisant la phrase de description, expliquer ce que renvoie la fonction (le type et le contenu de la réponse).
...CORRECTION...
Il est noté que bin() est une fonction build-in, "construire à l'interne" en traduction mot à mot.
Un seul de type int. C'est décrit de deux façons : une indication dans la phrase de description et une indication dans l'exemple d'utilisation qu'on trouve en bas de documentation.
La fonction renvoie une représentation de l'entier fourni sous forme binaire, fournie via un string.
Nous allons maintenant voir comment écrire les documentations de vos fonctions personnelles.
2.3 DOCUMENTATION rapide : le prototype + une phrase
1
2
3
defaddition(nbr1:int, nbr2:int) -> int:"""Renvoie la somme de nbr1 et nbr2"""returnnbr1+nbr2
Nous avions vu la signature d'un opérateur (comme +) : int+int->int
Nous allons voir le même principe pour les fonctions.
2.3.1 - Signature d'une fonction
Arguments
⇒
Fonction
⇒
Réponse
Pour pouvoir utiliser une fonction, on a besoin :
de savoir comment elle se nomme.
de savoir combien d'arguments lui envoyer et leurs types.
de savoir ce qu'elle va répondre.
Toute ces informations se retrouvent dans la signature. Par exemple, la signature d'une fonction addition() qui permet d'additionner deux entiers :
addition(int, int) ->int
2.3.2 - Prototype d'une fonction
Prototyper une fonction consiste à rajouter les noms des paramètres à la signature.
addition(nombre1:int, nombre2:int) ->int
Attention : le prototype ne donne pas d'indications sur ce que réalise la fonction. On peut s'en douter en regardant les noms de la fonction et des paramètres si ils sont explicites, mais on ne fait que deviner. On peut donc se tromper.
2.3.3 - Spécification : la documentation minimale d'une fonction
Spécifier une fonction consiste à donner :
Le PROTOTYPE qui fixe la SYNTAXE et le TYPAGE
Une description nommée DOCSTRING qui fixe clairement la SEMANTIQUE
Sous la première ligne du prototype, on rajoute donc un string explicatif :
qu'on place juste sous le prototype,
délimité par trois guillemets d'ouverture et de fermeture.
Exemple
Voilà la spécification de notre fonction-exemple :
1
2
3
defaddition(nbr1:int, nbr2:int) -> int:"""Renvoie la somme de nbr1 et nbr2"""returnnbr1+nbr2
2.3.4 - Obtenir la documentation
La fonction help() renvoie les informations fournies dans la documentation.
>>> help(addition)
Help on function addition in module __main__:addition(nbr1: int, nbr2: int) -> int Renvoie la somme de nbr1 et nbr2
10-A° Modifier alors calculer_moyenne() pour qu'elle renvoie la réponse attendue : il faudra lire la documentation pour le savoir.
1
2
3
4
5
defcalculer_moyenne(note1:int, note2:int) -> float:"""Renvoie la moyenne des deux notes envoyées"""return0.0m=calculer_moyenne(10, 20)
Remarquez bien que pour le moment, la fonction renvoie bien une réponse du bon type (un float) mais c'est toujours 0.0 pour l'instant. A vous de faire le travail.
Exemple d'utilisation dans la console interactive (après avoir mis vos modifications de la fonction en mémoire !)
defcalculer_moyenne(note1:int, note2:int) -> float:"""Renvoie la moyenne des deux notes envoyées"""return(note1 + note2)/2m=calculer_moyenne(10, 20)
2.4 DOCUMENTATION longue : le docstring multiligne
2.4.1 Limitation de la documentation rapide
Parfois, la documentation rapide ne suffit pas. Par exemple, cette fonction vitesse() :
1
2
3
defvitesse(distance:int, duree:int) -> float:"""Renvoie la vitesse connaissant la distance parcourue et la durée"""returndistance/duree
Problème 1 : on ne signale pas à l'utilisateur qu'il ne doit surtout pas fournir de durée nulle pour éviter une division par zéro.
Problème 2 : on ne signale pas si il y a des unités attendues pour durée et distance.
2.4.2 Solution : Docstring multiligne
On peut fournir une vraie documentation sur plusieurs lignes.
Il n'existe aucune codification imposée pour cette documentation. Voici les éléments qu'elle doit contenir, quelque soit la façon de l'exprimer :
DESCRIPTION : une description de ce que fait la fonction
PROTOTYPAGE : l'équivalent de la signature et du prototype mais sous forme d'un texte plus libre
PRECONDITION : une explication claire des conditions supplémentaires sur les paramètres d'entrée si certaines valeurs posent problème.
...
1
2
3
4
5
6
7
8
9
defvitesse(distance, duree):"""Renvoie la vitesse connaissant la distance parcourue et la durée
:: param distance(int) :: la distance en m
:: param duree(int) :: la durée NON NULLE en s
:: return (float) :: la vitesse en m.s-1
"""returndistance/duree
10-B° Lire la nouvelle documentation de calculer_moyenne(). Répondre aux questions suivantes :
Sur quelles lignes trouve-t-on les types attendus des deux paramètres ?
Quelles sont les préconditions qu'on trouve dans cette documentation plus longue ?
Sur quelle ligne se trouve l'indication sur le type de la réponse dans cette documentation ?
1
2
3
4
5
6
7
8
9
10
11
defcalculer_moyenne(note1, note2):"""Renvoie la moyenne des deux notes :: param note1(int) :: une note dans [0;20] :: param note2(int) :: une note dans [0;20] :: return (float) :: la moyenne de note1 et note2 """return(note1+note2)/2moy=calculer_moyenne(10, 20)
Enfin, lancer le programme dans Thonny pour vérifier que la fonction fonctionne de la même façon que celle de la question précédente mais que la documentation est juste plus fournie lorsqu'on utilise help().
>>> moy
15.0>>> moy2 = calculer_moyenne(10, 15)
>>> moy2
12.5>>> help(calculer_moyenne)
Help on function calculer_moyenne in module __main__:
calculer_moyenne(note1, note2)
Renvoie la moyenne des deux notes
:: param note1(int) :: une note dans [0;20]
:: param note2(int) :: une note dans [0;20]
:: return (float) :: la moyenne de note1 et note2
...CORRECTION...
Sur quelles lignes trouve-t-on les types attendus des deux paramètres ?
4
5
:: param note1(int) :: une note dans [0;20] :: param note2(int) :: une note dans [0;20]
Quelles sont les préconditions sur les paramètres qu'on trouve dans cette documentation plus longue ?
4
5
:: param note1(int) :: une note dans [0;20] :: param note2(int) :: une note dans [0;20]
Sur quelle ligne se trouve l'indication sur le type de la réponse dans cette documentation ?
6
:: return (float) :: la moyenne de note1 et note2
11° Compléter les fonctions addition(), multiplication() et division_euclidienne() pour qu'elles fonctionnent correctement. Enregistrer et lancer dans Thonny pour placer cette fonction en mémoire. Faire les appels dans la console pour vérifier le fonctionnement.
defaddition(note1, note2):"""Renvoie la somme des deux notes :: param note1(int) :: une note dans [0;20] :: param note2(int) :: une note dans [0;20] :: return (int) :: la somme de note1 et note2 """return0defmultiplication(x, y):"""Renvoie la multiplication de x par y :: param x(int) :: un entier :: param y(int) :: un entier :: return (int) :: x*y """return0defdivision_euclidienne(a, b):"""Renvoie le quotient et le reste de la division euclidienne de a par b :: param a(int) :: un entier :: param b(int) :: un entier NON NUL :: return (tuple) :: le couple (quotient, reste) """return(0,0)
defaddition(note1, note2):"""Renvoie la somme des deux notes :: param note1(int) :: une note dans [0;20] :: param note2(int) :: une note dans [0;20] :: return (int) :: la somme de note1 et note2 """returnnote1+note2defmultiplication(x, y):"""Renvoie la multiplication de x par y :: param x(int) :: un entier :: param y(int) :: un entier :: return (int) :: x*y """returnx*ydefdivision_euclidienne(a, b):"""Renvoie le quotient et le reste de la division euclidienne de a par b :: param a(int) :: un entier :: param b(int) :: un entier NON NUL :: return (tuple) :: le couple (quotient, reste) """return(a//b,a%b)
12° Placer la fonction en mémoire. Réaliser les appels proposés pour comprendre ce que fait cette fonction.
Question A : écrire la documentation complète (plusieurs lignes) de cette fonction une fois que vous avez compris ce qu'elle fait.
Question B : écrire la documentation en utilisation une documentation rapide (prototype et une seule ligne).
1
2
3
4
5
6
7
8
9
10
importrandomdefde(nb_faces):'''La petite phrase qui va bien :: à faire : décrire le paramètre :: :: à faire : décrire la réponse :: '''returnrandom.randint(1,nb_faces)
>>> import random
>>> help(random.randint)
Help on method randint in module random:randint(a, b) method of random.Random instance
Return random integer in range [a, b], including both end points.
...CORRECTION...
1
2
3
4
5
6
7
8
9
10
importrandomdefde(nb_faces):'''Renvoie un nombre aléatoire entre 1 et nb_faces inclus. :: nb_faces(int) : valeur maximale voulue pour le dé :: :: return (int) : un entier aléatoire dans l'intervalle [1; nb_faces] :: '''returnrandom.randint(1,nb_faces)
1
2
3
4
5
importrandomdefde(nb_faces:int)->int:'''Renvoie un nombre aléatoire entre 1 et nb_faces inclus.'''returnrandom.randint(1,nb_faces)
13° Réaliser la fonction telle qu'elle est spécifiée dans sa documentation :
1
2
3
defest_long(chaine:str)->bool:"""Fonction qui renvoie True si la chaine est d'au moins 6 caractères, False sinon"""pass
Remarque : Une fonction sans instruction provoque une erreur. L'instruction pass porte bien son nom : elle permet de fournir une instruction de façon à ne pas provoquer d'erreur de syntaxe mais cette instruction veut dire "Ne fait rien". On place donc souvent pass comme instruction temporaire.
...CORRECTION...
1
2
3
defest_long(chaine:str)->bool:"""Fonction qui renvoie True si la chaine est d'au moins 6 caractères, False sinon"""returnlen(chaine)>=6
14° Réaliser la fonction telle qu'elle est spécifiée dans sa documentation :
1
2
3
4
5
6
7
8
9
10
11
defcontient(chaine1:str,chaine2:str)->bool:"""Prédicat qui renvoie True si chaine1 est bien dans chaine2, False sinon :: exemple .. >>> contient('Bonjour', 'Bon, alors bonjour') False >>> contient('bonjour', 'Bon, alors bonjour') True """pass
...CORRECTION...
1
2
3
4
5
6
7
8
9
10
11
defcontient(chaine1:str,chaine2:str)->bool:"""Prédicat qui renvoie True si chaine1 est bien dans chaine2, False sinon :: exemple .. >>> contient('Bonjour', 'Bon, alors bonjour') False >>> contient('bonjour', 'Bon, alors bonjour') True """returnchaine1inchaine2
Que se passe-t-il lorsque la fonction rencontre un return ?
A quel moment sort-on vraiment de la fonction ?
15° Observer la fonction centaine(). On a demandé à quelqu'un de compléter la fonction suivante pour qu'elle renvoie le chiffre des centaines d'un nombre envoyé en argument. Il a tapé ceci en oubliant de supprimer la ligne qu'on lui avait donné, celle avec le return 0.
1
2
3
4
5
6
7
8
9
10
defcentaine(x):"""Renvoie le chiffre de la centaine de x :: param x(int) :: un entier :: return (int) :: la centaine de x (5 si 1545) """return0mon_petit_calcul=x//100%10returnmon_petit_calcul
Tester la fonction dans la console.
Questions
Pourquoi cette fonction renvoie-t-elle TOUJOURS 0 ?
Modifier la fonction pour qu'elle fonctionne correctement en évitant les étapes superflues.
Si vous ne voyez pas, Python Tutor est votre ami.
...CORRECTION...
On aurait pu croire que la fonction suit son cours et finit par répondre à la fin. Mais non !
Dès qu'on rencontre un return, la fonction renvoie ce qu'on a placé derrière et on sort immédiatement.
Dès qu'on arrive sur la ligne 8, on renvoie 0. Et on sort de la fonction.
Il ne sert strictement à rien de placer des choses sur des lignes qui suivent le return : ces lignes ne seront jamais analysées.
Ici, les lignes 09 et 10 ne servent strictement à rien. C'est comme si vous aviez tapé ceci :
1
2
3
4
5
6
7
8
9
10
defcentaine(x):"""Renvoie le chiffre de la centaine de x :: param x(int) :: un entier :: return (int) :: la centaine de x (5 si 1545) """return0
Après correction, vous devriez avoir ceci :
1
2
3
4
5
6
7
8
defcentaine(x):"""Renvoie le chiffre de la centaine de x :: param x(int) :: un entier :: return (int) :: la centaine de x (5 si 1545) """returnx//100%10
3.1 FONCTION : sortie immédiate avec return
Dès que la fonction rencontre un return :
elle évalue l'expression fournie puis
elle renvoie sa réponse, puis
son espace local des noms est détruit définitivement.
Lorsqu'on rencontre le return, on sort immédiatement. Ok.
Mais...
Et si on ne rencontrait jamais de return ? Que se passe-t-il ?
Nous allons voir que la fonction répond quelque chose quand même et cette réponse est ... vide. Mais une réponse "vide" contient une information : l'information que la fonction est bien terminée puisqu'elle a répondu.
16° Observer le code suivant qui possède une fonction sans return :
1
2
3
defdivision(x:int, y:int):"""Calcule la division entière de x par y, mais n'en fait rien..."""mon_petit_calcul=x//y
Tester la fonction dans la console :
>>> division(7, 2)
>>>
Puisque division(7,2) est la seule chose présente sur la ligne, sa réponse aurait du être transmise dans la console. Or, on constate que rien ne s'affiche. En apparence, la fonction n'a rien répondu.
Une fonction Python renvoie au moins la valeur None lorsqu'elle se termine sans avoir rencontré return.
On parle parfois de procédure pour désigner une fonction qui ne répond rien. Les procédures n'existent donc pas vraiment en Python.
La ligne 4 ci-dessous est donc totalement facultative :
1
2
3
4
defdivision(x:int, y:int) -> None:"""Calcule la division entière de x par y, mais n'en fait rien..."""mon_petit_calcul=x//yreturnNone
3.2.2 Attention au stockage de la réponse...
Il arrive souvent qu'on utilise mal de telles fonctions qui renvoient None si on a lu trop vite la documentation.
Le cas typique vient de la méthode append() qui permet de rajouter une case à la fin d'un tableau Python : elle modifie le tableau mais ne renvoie "rien".
Bonne utilisation
>>> t = [10, 20, 30]
>>> t.append(100)
>>> t
[10, 20, 30, 100]
Mauvaise utilisation
Si quelqu'un l'utilise mal en pensant qu'elle répond en donnant la nouvelle version du tableau, il va écraser le tableau et y placer None !
>>> t = [10, 20, 30]
>>>t = t.append(100)
>>> t
Ici, t contient maintenant None.
Attention : si on utilise append(), il ne s'agit plus d'un tableau statique mais d'un tableau dynamique. Nous les utiliserons plus tard dans l'année.
3.2.3 None et les booléens
Lorsqu'on veut évaluer un contenu sous forme de booléen, None est considéré comme le cas nul ou vide.
>>> bool(None)
False
Mais attention : None n'est identique à False.
>>> a = None
>>> a == False
False>>> bool(a) == False
True
Cette partie ne comporte aucun attendu du programme de NSI.
Par contre, elle vous permettra de mieux comprendre certains codes que vous pourriez trouver sur le Web et vous simplifier la vie lors de vos projets.
4.1 FONCTION : Paramètres par défaut
4.1.1 Intérêt
Imaginons une fonction qui renvoie un résultat aléatoire compris entre un entier debut et un entier fin :
1
2
3
4
5
6
7
8
9
10
11
importrandomdefaleatoire(debut,fin):"""Renvoie un entier aléatoire entre debut et fin :: param debut(int) :: un entier :: param fin(int) :: un entier SUPERIEUR à debut :: return (int) :: un entier dans [debut, fin] """returnrandom.randint(debut,fin)
Si cette fonction doit simuler un dé, l'entier debut sera toujours 1. Et pourtant, il faut toujours lui fournir cette valeur puisqu'il y a deux arguments à envoyer.
Si on ne met qu'un seul paramètre attendu et qu'on impose que la valeur de départ est toujours 1, on obtient une fonction moins flexible.
Comment avoir le meilleur des deux mondes ?
4.1.2 Paramètres par défaut
On peut donner des valeurs d'arguments par défaut aux fonctions. Si l'utilisateur ne fournit pas de valeurs pour ces paramètres, il sera rempli automatiquement avec la valeur située un signe = dans la déclaration.
1
2
3
4
5
6
7
8
9
10
11
importrandomdefaleatoire(fin,debut=1):"""Renvoie un entier aléatoire entre debut et fin :: param fin(int) :: un entier SUPERIEUR à debut :: param debut(int) :: un entier :: return (int) :: un entier dans [debut, fin] """returnrandom.randint(debut,fin)
On peut alors faire des appels en envoyant la valeur de départ, ou pas.
>>> aleatoire(50, 40)
43>>> aleatoire(50)
12
Attention au positionnement : remarquez bien qu'il faut que les paramètres ayant une valeur par défaut soient positionnés derrière les paramètres sans valeur par défaut. C'est pour cela que debut=1 est déclarée derrière fin.
4.1.3 Documentation
On peut bien entendu continuer à indiquer rapidement le type des paramètres, même avec des valeurs par défaut.
1
2
3
4
5
importrandomdefaleatoire(debut:int=1,fin:int=1):"""Renvoie un entier aléatoire entre debut et fin"""returnrandom.randint(debut,fin)
4.2 FONCTION : Paramètres nommés ou positionnels
4.2.1 Paramètres nommés
Cette fonctionnalité est présente de base dans Python : plutôt que de fournir les arguments dans l'ordre imposé par l'ordre des paramètres dans la déclaration, on peut les fournir comme on le veut, pourvu de tous les fournir.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
importrandomdefsomme_aleatoire(nb,debut,fin):"""Renvoie la somme de x lancés aléatoire entre debut et fin :: param nb(int) :: POSITIF, le nombre de lancés voulus :: param debut(int) :: un entier :: param fin(int) :: un entier SUPERIEUR à debut :: return (int) :: un entier dans [nb*debut, nb*fin] """s=0# Compteur-mémoire de la sommefor_inrange(nb):# Fait l'action nb foiss=s+random.randint(debut,fin)# Incrémente s d'un nombre aléatoirereturns
>>> somme_aleatoire(10, 1, 6) # on demande 10 dés à 6 faces>>> somme_aleatoire(nb=10, debut=1, fin=6) # on demande 10 dés à 6 faces>>> somme_aleatoire(debut=1, fin=6, nb=10) # on demande 10 dés à 6 faces
4.2.2 Paramètres positionnels uniquement
Nommer les paramètres, c'est pratique mais dangereux. Un changement de nom et tous les programmes utilisant l'ancienne version ne fonctionneront plus.
On peut empêcher l'utilisation des paramètres nommés et donc imposer que seul l'ordre de transfert des arguments soit important.
Pour cela, il suffit de rajouter un symbole / dans la liste des paramètres.
Les paramètres à gauche du / sont positionnels uniquement (nb).
Les paramètres à droite du / sont nommables lors de l'appel (debut et (fin)).
>>> somme_aleatoire(10, 1, 6) # on demande 10 dés à 6 faces>>> somme_aleatoire(10, debut=1, fin=6) # on demande 10 dés à 6 faces>>> somme_aleatoire(nb=10, debut=1, fin=6) # ECHEC : on tente de nommer le premier paramètre !TypeError: somme_aleatoire() got some positional-only arguments passed as keyword arguments: 'nb'
4.3 FONCTION : documentation des types construits
Selon les versions de Python, il est possible, ou pas, d'écrire ce que contient un type construit.
Exemple avec une fonction dont le paramètre doit être un tableau d'entiers.
1
2
deff(x:list[int]):
...
Pour un dictionnaire dont les clés sont des strings et les valeurs sont des integers :
1
2
deff(x:dict[(str, int)]):
...
Si la version de Python que vous utilisez provoque une erreur et que vous ne pouvez pas en changer pour une raison ou une autre, il suffit de modifier la documentation pour fournir qu'un string :
L6(appel) - L3(réception des arguments) - L4(envoi) - L6(réception et stockage)
L7(appel) - L3(réception des arguments) - L4 (envoi) - L7(réception et stockage).
Fin
19° Répondre aux questions suivantes liées au programme fourni :
1
2
3
4
5
defm(a:int,b:int):return(a+b)/2x=m(2,6)a=m(10,20)
Ligne 4
Quels sont les arguments envoyés à la fonction m lors de cet appel ?
A quoi fait référence le paramètre a lors de cet appel ?
A quoi fait référence le paramètre b lors de cet appel ?
A quoi fait référence la variable globale x après cet appel ?
Ligne 5
Quels sont les arguments envoyés à la fonction m lors de cet appel ?
A quoi fait référence le paramètre a lors de cet appel ?
A quoi fait référence le paramètre b lors de cet appel ?
A quoi fait référence la variable globale a après cet appel ?
...CORRECTION...
20° Donner la ligne et le nom de la seule variable globale de ce programme.Fournir ensuite la succession des lignes suivies lors de l'exécution de ce programme. Enfin, fournir le contenu récupérée dans la variable globale.
La seule variable globale est la variable x de la ligne 7, qui n'a rien à voir avec la variable locale de la ligne 1.
L1(déclaration) - L4(déclaration) - L7(appel en envoyant 2 et 6) - L4(réception de 2 dans a et 6 dans b) - L5(appel en envoyant a et b donc 2 et 6) - L1(réception de 2 dans x locale et 6 dans y locale) - L2(calcul de 2+6=8 et envoi du 8) - L5(retour du 8, calcul de 8*1000=8000 et envoi du 8000) - L7(retour du 8000 qui est alors référencé par la variable x globale).
J'ai entendu parler de routine, de procédure. C'est quoi ?
Le mot fonction est utilisé de façon assez générique dans Python. Par contre en informatique, il existe des entités qui peuvent être gérées de façon différente selon les langages.
Le vrai mot générique pour parler d'une suite d'instructions mémorisées et à laquelle on peut faire appel de nombreuses fois est le mot routine.
On distinguera deux types de routine :
Si la routine ne renvoie rien (pas de return), on parle de procédure.
Si la routine renvoie bien quelque chose vers le programme d'appel, on parle de fonction.
Du coup, les vraies procédures n'existent pas en Python : même si votre fonction ne renvoie rien, elle renvoie None. Cela permet de savoir que la fonction n'a rien renvoyé mais ce n'est pas vraiment ...rien... En effet, si la variable reponse existe et contient None, nous avons appris quelque chose : notre fonction est terminée.
On peut détruire une variable ?
Oui, on peut libérer la place mémoire attribuée à une variable. Pour cela, il faut utiliser le mot-clé del.
Exemple :
>>> a = 5
>>> a
5>>> del a
>>> a
NameError: name 'a' is not defined
Activité publiée le 28 08 2019
Dernière modification : 15 07 2023
Auteur : ows. h.