fonction le return

Identification

Infoforall

18 - Le Return des paramètres


Aujourd'hui, nous allons voir un peu plus en détails les fonctions, leurs paramètres et leurs retours.

Logiciel nécessaire pour l'activité : Python 3

Evaluation ✎ : questions -

Documents de cours : open document ou pdf

Vocabulaire

Une procédure est une routine qui ne renvoie pas de réponse. Elles n'existent pas en Python, mais ce qui leur ressemble le plus est une fonction qui répond systématiquement None.

Une fonction est une routine qui renvoie une réponse à l'aide du mot-clé return.

1 - Paramètres d'une fonction

Paramètres

1.1 Idée abstraite d'une fonction

Une fonction peut être vue comme une boîte noire qui reçoit des données en ENTREE, qui réalise un travail sur ces données puis qui fournit une réponse en SORTIE.

 ⇒   FONCTION   ⇒ 
ENTREE(S) SORTIE

Exemple d'une fonction capable de recevoir deux informations et de renvoyer un calcul basé sur les informations reçues.

         a  ⇒   FONCTION   ⇒ 
(a+b)*10
         b

1.2 Python : déclaration de la fonction et de ses paramètres

Lors de la déclaration d'une fonction avec le mot-clé def, on peut lui demander de récupérer les données qu'on va lui envoyer en ENTREE dans des variables locales un peu particulière qu'on nomme les paramètres de la fonction.

Exemple : voici une fonction f qui doit recevoir deux données d'entrée a et b et renvoyer leur somme multipliée par dix :

1 2
def f(a, b): return (a+b)*10

C'est bien sur la ligne 1 qu'on signale qu'on veut créer une fonction nommée f et lui attribuer deux paramètres a et b.

On notera bien

  1. qu'il s'agit de variables locales : elles n'existeront que pendant l'exécution réelle de la fonction et disparaitront une fois qu'elle aura répondu.
  2. que leurs contenus réels ne sont connus que lors de l'appel de la fonction.

1.3 - Arguments d'appel et paramètres de fonction

C'est lorsqu'on appelle concrètement la fonction qu'on va devoir lui envoyer les informations sur lesquelles on veut la voir travailler.

1 2 3 4 5
def f(a, b): return (a+b)*10 x = f(2, 6) y = f(1, x)

En ligne 4, nous voyons qu'on lance à un appel à la fonction f en lui demandant de travailler sur les données 2 et 6. On nomme arguments les données envoyées à la fonction.

Pour savoir où vont être stockés ces arguments, il faut comparer l'appel et la déclaration de la fonction :

1 4
def f(a, b): x = f(2, 6)

On voit donc que

  • l'argument 2 va être stocké dans le paramètre a. Le paramètre a fera donc référence à 2 lors de cette exécution. C'est un peu comme si on avait tapé a = 2.
  • l'argument 6 va être stocké dans le paramètre b. Le paramètre a fera donc référence à 6 lors de cette exécution. C'est un peu comme si on avait tapé b = 6.

La fonction va donc travailler et renvoyer (2+6)*10, soit 80.

On stocke ce 80 dans la variable globale x.

En résumé :

  • Arguments : ce qu'on envoie à la fonction lors d'un appel.
    • Moyen mnémotechnique : A comme lors de l'Appel.
  • Paramètres : les variables locales de la fonction qui vont recueillir les arguments envoyés.
    • Moyen mnémotechnique : P comme lors du Prototypage.

01° Répondre aux questions suivantes liées au programme fourni :

1 2 3 4 5
def f(a, b): return (a+b)*10 x = f(2, 6) y = f(1, x)

On considère qu'après exécution de la ligne 4, x contient 80 comme expliqué dans l'encadré ci-dessus.

  1. Ligne 5 : quels sont les arguments envoyés à la fonction f lors de cet appel ?
  2. A quoi fait référence le paramètre a lors de cet appel ?
  3. A quoi fait référence le paramètre b lors de cet appel ?
  4. Que contient la variable globale y après avoir reçu la réponse de la fonction ?

...CORRECTION...

Le plus facile est de mettre en concordance la ligne de la déclaration et la ligne de l'appel.

1 5
def f(a, b): y = f(1, x)
  1. La ligne 5 permet de voir qu'on envoie les arguments 1 et x à notre fonction.
  2. Le paramètre a va donc faire référence à 1.
  3. Le paramètre b va donc faire référence à x. On peut donc dire que b_de_la_fonction et x_du_programme sont des alias du même contenu mémoire : 80
  4. Il suffit donc de faire le calcul (1+80)*10, soit 810. La variable y fait donc référence à 80 une fois que la fonction a répondu une nouvelle fois.

02° Répondre aux questions suivantes liées au programme fourni :

1 2 3 4 5
def m(a, b): return (a+b)/2 x = m(2, 6) a = m(10, 20)
  1. 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 ?
    • Que contient la variable globale x après avoir reçu la réponse de la fonction ?
  2. 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 ?
    • Que contient la variable globale a après avoir reçu la réponse de la fonction ?

...CORRECTION...

Le plus facile est de mettre en concordance la ligne de la déclaration et la ligne de l'appel.

1 4
def m(a, b): x = m(2, 6)

La ligne 4 permet de voir qu'on envoie les arguments 2 et 6 à notre fonction.

Le paramètre a va donc faire référence à 2.

Le paramètre b va donc faire référence à 6.

Il suffit donc de faire le calcul (2+6)/2, soit 4.0. La variable x fait donc référence à 4.0 une fois que la fonction aura répondu.

La ligne 5 n'est pas plus compliquée, j'ai juste utilisé le même nom pour la variable globale et pour l'un des paramètres mais il suffit de rajouter mentalement "_de_la_fonction" ou "_du_programme" pour comprendre qu'il ne s'agit pas de la même variable.

1 5
def m(a, b): a = m(10, 20)

La ligne 5 permet de voir qu'on envoie les arguments 10 et 20 à notre fonction.

Le paramètre a va donc faire référence à 10.

Le paramètre b va donc faire référence à 20.

Il suffit donc de faire le calcul (10+20)/2, soit 15.0. La variable globale a fait donc référence à 15.0 une fois que la fonction aura répondu.

2 - Une documentation claire

La programmation est, avant tout, une affaire de communication.

2-1 Communication programmeur humain - interpréteur Python

Commençons par voir ce qui se passe si le programmeur communique mal avec l'interpréteur Python.

Erreur de syntaxe

Les fonctions que vous écrivez en Python doivent respecter la syntaxe de Python de façon à ce que votre texte soit compréhensible par l'interpréteur Python qui ne peut pas s'adapter à un texte mal conçu.

Si vous ne suivez pas les règles, l'interpréteur ne vous comprendra pas et va vous le dire en signalant en rouge une erreur de syntaxe : votre demande ne respecte pas sa façon de communiquer.

Exemple 1

>>> print "bonjour" File "<pyshell>", line 1 print "bonjour" ^ SyntaxError: Missing parentheses in call to 'print'. Did you mean print("bonjour")?

L'interpréteur Python est perdu car on a oublié de placer les parenthèses autour de l'argument envoyé.

Exemple 2

1 2 3 4 5
def f(a, b) return (a+b)*10 x = f(2, 6) y = f(1, x)
>>> %Run script_essai.py def f(a,b) ^ SyntaxError: invalid syntax

L'interpréteur Python est perdu car on a oublié de placer le : final de la déclaration de la fonction.

2-2 Communication utilisateur humain - concepteur humain

Reste encore un problème majeur de communication : celle entre la personne qui a conçu une fonction et la personne qui veut juste l'utiliser.

Dans ce cas, la communication passe par la documentation de la fonction.

Documentation : pour savoir comment utiliser la fonction

Il faut documenter les fonctions : la documentation doit expliquer comment utiliser la fonction. Une sorte de mode d'emploi de votre fonction. Ces informations doivent être suffisantes pour utiliser la fonction sans provoquer d'erreur.

Les délimiteurs de la documentation sont 3 guillemets simples en Python.

La documentation doit contenir au minimum :

  1. Une courte phrase expliquant à quoi elle sert
  2. Les preconditions : il s'agit des contraintes sur les types et les valeurs possibles des paramètres.
  3. Les postconditions : il s'agit cette fois d'une description de la sortie renvoyée par la fonction

Exemple

1 2 3 4 5 6 7 8 9 10
def moyenne(a, b): '''Renvoie la moyenne des notes a et b :: param a(int|float) : nombre dans l'intervalle [ 0 ; 20 ] :: param b(int|float) : nombre dans l'intervalle [ 0 ; 20 ] :: return (float): la moyenne des deux notes, dans [ 0.0 ; 20.0 ] ''' m = (a + b) / 2 return m

Si on lit cette documentation :

  1. On peut lire en ligne 2 qu'elle calcule une moyenne
  2. 2
    '''Renvoie la moyenne des notes a et b
  3. Sur les lignes 5-, on indique qu'on doit lui envoyer deux notes sous forme d'un nombre compris entre 0 et 20, inclus.
  4. 4 5
    :: param a(int|float) : nombre dans l'intervalle [ 0 ; 20 ] :: param b(int|float) : nombre dans l'intervalle [ 0 ; 20 ]

    La barre verticale | (ALT-GR+6) veut dire OU dans ce contexte. On voit donc que le nombre peut être transmis soit sous forme d'integer ou de flottant.

  5. La ligne 6 permet de comprendre que la fonction va renvoyer un nombre compris entre 0 et 20 et que son type sera float.
  6. 6
    :: return (float): la moyenne des deux notes, dans [ 0.0 ; 20.0 ]

Récupérer la documentation

Pour récupérer la documentation d'une fonction mise en mémoire, il suffit d'utiliser la fonction native help(). Pratique lorsque votre projet fait 300000 lignes.

>>> help(moyenne) Help on function moyenne in module __main__: moyenne(a, b) Renvoie la moyenne des notes a et b :: param a(int|float) : nombre dans l'intervalle [ 0 ; 20 ] :: param b(int|float) : nombre dans l'intervalle [ 0 ; 20 ] :: return (float): la moyenne des deux notes, dans [ 0.0 ; 20.0 ]

03° Placer le programme suivant dans Thonny. Lancer les appels proposés dans la console puis répondre aux questions ci-dessous.

1 2 3 4 5 6 7 8 9 10
def moyenne(a, b): '''Renvoie la moyenne des notes a et b :: param a(int|float) : nombre dans l'intervalle [ 0 ; 20 ] :: param b(int|float) : nombre dans l'intervalle [ 0 ; 20 ] :: return (float): la moyenne des deux notes, dans [ 0.0 ; 20.0 ] ''' m = (a + b) / 2 return m
>>> moyenne(0, 0) 0.0 >>> moyenne(0.0, 20.0) 10.0 >>> moyenne(14.0, 20) 17.0 >>> moyenne(14.0, 32) 23.0
  1. Sur quelle ligne se trouve la courte description de la fonction ?
  2. Est-il possible que la sortie ne soit pas un nombre compris entre 0 et 20 si les deux paramètres sont bien des nombres compris entre 0 et 20 
  3. Est-il possible que la sortie ne soit pas un nombre compris entre 0 et 20 si l'un des deux paramètres ne respecte pas les préconditions 
  4. L'utilisateur de la fonction est-il obligé de suivre les préconditions ?

...CORRECTION...

Réponse A

La courte phrase se trouve en ligne 2, juste sous la ligne du prototype.

Réponse B

Si les deux nombres sont bien compris entre 0 et 20, il est évident que la moyenne des deux ne pourra qu'être dans l'intervalle [0; 20] également.

>>> moyenne(0, 0) 0.0 >>> moyenne(0.0, 20.0) 10.0 >>> moyenne(14.0, 20) 17.0

Réponse C

Par contre, on voit avec le dernier exemple que si l'utilisateur n'a pas respecter les contraintes imposées sur les paramètres, la sortie ne respecte plus non plus les contraintes notées. On peut donc dire que si les préconditions ne sont pas respectées, l'utilisateur ne peut plus considérer les yeux fermés que la postcondition soit vraie non plus.

>>> moyenne(14.0, 32) 23.0

Réponse D

Puisque nous sommes parvenus à taper l'exemple précédent, c'est bien que rien n'impose d'utiliser la fonction n'importe comment !

Par contre, si on tape n'importe quoi en entrée, on en paye le prix : on ne peut plus se baser sur la postcondition ou cela peut même provoquer une erreur.

04° Quelqu'un tape ceci. L'erreur est-elle imputable à l'utilisateur de la fonction ou au concepteur de la fonction ?

Pour répondre, il suffit de se demander si l'utilisateur a bien respecté les préconditions, ou pas.

>>> moyenne("4", "20") File "/home/rv/Documents/TESTS_PROG/test_python/activite.py", line 9, in moyenne m = (a + b) / 2 TypeError: unsupported operand type(s) for /: 'str' and 'int'

...CORRECTION...

La documentation permet de trancher : les paramètres doivent être des integers ou des floats. Envoyer un string ne correspond donc pas aux préconditions. C'est donc bien la faute de l'utilisateur.

05° Voici une fonction permettant de jouer à pierre-feuille-ciseaux. Quelqu'un tape ceci. L'erreur est-elle imputable

  • à l'utilisateur de la fonction (car il n'a pas respecter les préconditions visibles lors de ses appels à la fonction) ?
  • au concepteur de la fonction (car il n'a pas bien réalisé la fonction et elle ne respecte pas la postcondition) ?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
def pfc(a, b): '''Renvoie 1 ou 2 indiquant le joueur gagnant le pierre-feuille-ciseaux :: param a(str) : "pierre", "feuille" ou "ciseaux" :: param b(str) : "pierre", "feuille" ou "ciseaux" :: return (int) : le numéro du joueur qui gagne (1 ou 2 donc) ''' gagnant = 2 # le gagnant par défaut est le joueur 2 if a == "pierre" and b == "ciseaux": gagnant = 1 elif a == "ciseaux" and b == "feuille": gagnant = 1 elif a == "feuille" and b == "piere": gagnant = 1 return gagnant
>>> pfc("pierre", "feuille") 2

Ok , c'est bien le joueur 2 qui gagne avec sa "feuille" qui entoure la "pierre".

>>> pfc("feuille", "pierre") 2

La fonction déclare que c'est le joueur 2 qui gagne. Problème, c'est le joueur 1 qui gagne normalement !

>>> pfc("feuille", "feuille") 2

Nouveau problème, c'est une égalité normalement !

...CORRECTION...

Cette fois, on voit bien que l'utilisateur a totalement respecté les préconditions.

Or, la fonction ne répond pas correctement. Cette fois, c'est la faute du concepteur de la fonction.

Si vous cherchez d'où viennent les erreurs de conception, il y a déjà "pierre" écrit "piere", avec un seul r, ligne 14. L'erreur de l'égalité vient du fait que le concepteur dit lui même qu'on répond 1 ou 2, il n'a pas géré l'égalité. Sa copie est à revoir.

A COMPRENDRE (IMPORTANT) : préconditions et postconditions

Contrat de confiance

Les préconditions sont donc les conditions ou contraintes que l'utilisateur de la fonction doit respecter.

Il y a donc une sorte de contrat de confiance entre utilisateur et concepteur :

  • L'utilisateur est le garant du respect des préconditions lors des appels
  • Le concepteur garantit que la postcondition est alors vérifiée (sous condition du respect des préconditions donc).

Premier cas : l'utilisateur respecte les préconditions

Dans ce cas, l'utilisateur peut concevoir le reste de son programme en considérant que la postcondition est vraie : le concepteur de la fonction s’y est engagé.

Si la fonction donne une mauvaise réponse ou provoque une exception, c'est la faute du concepteur car l'utilisateur a envoyé des données respectant les préconditions.

Deuxième cas : l'utilisateur ne respecte pas les préconditions

Dans ce cas, on ne peut pas tenir compte de la postcondition.

La fonction peut

  • fonctionner correctement par hasard ou
  • donner une mauvaise réponse ou
  • provoquer une erreur.

En tous cas, c'est clairement la faute de l'utilisateur : il a utilisé la fonction hors des clous.

3 - Documentation rapide avec typing

Taper de la documentation permet de bien comprendre ce que fait une fonction. Par contre, c'est long...

Certaines fonctions ont une documentation qui dépasse de loin en taille le code de la fonction elle-même !

Pour ces fonctions, où le code n'est pas long et où on peut comprendre assez facilement ce qu'on veut faire, on peut passer par une documentation

  • moins longue (avantage) mais
  • moins précise (désavantage).

Exemple :

1 2 3
def convertir_sur_10(note:int) -> int: '''Renvoie une note sur 10 à partir d'une note sur 20''' return note // 2

Il s'agit bien d'une documentation : on dit que

  • le paramètre note devrait être un integer (note:int) et
  • que la fonction renvoie un integer (-> int).

Si la fonction ne renvoie rien (c'est une sorte de procédure), il suffit de dire qu'elle renvoie None, ou ne rien signaler. Mais explicite est mieux qu'implicite en informatique.

Exemple :

1 2 3 4 5 6
def afficher_le_plus_grand(a:int, b:int) -> None: '''Affiche la plus grande valeur dans la console''' if a > b: print(a) else: print(b)

Conclusion : cette forme de documentation est pratique mais moins complète qu'une vraie documentation. Elle est par contre très pratique pour fournir le prototype d'une fonction.

On peut ainsi indiquer le nom de la fonction, le nom et le type attendu des paramètres, et enfin le type de la réponse.

4 - FAQ

Paramètre formel et paramètre effectif ?

Il s'agit d'une autre façon de nommer les arguments et les paramètres.

Les données envoyées à la fonction lors d'un appel sont nommées soit 

  • Arguments (moyen mnémotechnique : A comme lors de l'Appel)
  • Paramètres effectifs
  • Paramètres réels

Les variables locales qui vont servir à récuper ces données dans la fonction sont nommées :

  • Paramètres (moyen mnémotechnique : P comme lors du Prototypage)
  • Paramètres formels

La prochaine activité vous montrera comment tester nos fonctions et notamment vérifier si les préconditions et postconditions sont bien respectées.

Activité publiée le 28 08 2019
Dernière modification : 05 02 2022
Auteur : ows. h.