fonction le return

Identification

Infoforall

9 - Le Return des paramètres


Dans la première activité sur les fonctions, nous avons comment déclarer, utiliser et documenter les fonctions..

Nous avons également vu que les variables définies dans les fonctions sont des variables locales, variables qui n'ont d'existence que le temps de l'exécution du code de la fonction.

Nous allons aujourd'hui étendre vos connaissances. Nous allons voir

  • La façon dont les variables globales interagissent avec les fonctions
  • La façon dont les paramètres des fonctions sont gérés
  • Un peu de précision sur les paramètres (et les arguments)

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

Evaluation ✎ : questions 02 -

Résumé : Version HTML ou fond blanc ou ou PDF (couleur ou gris)

On rappelle qu'une procédure est une routine qui ne renvoie pas de réponse.

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

Néanmoins, dans Python, fonction et procédure porte le même nom : functions. D'ailleurs, en Python, les procédures retournent une réponse même sans présence d'un return : None

1 - Spécifications d'entrée et de sortie

RAPPELS

Paramètres

Lors de la déclaration d'une routine (fonction, procédure ou méthode), on peut l'obliger à recevoir des informations, qu'on nomme des paramètres. Il s'agit simplement de variables locales, mais des variables dont le contenu sera déclaré lors de l'appel de la fonction.

Documentations

On peut insérer de la documentation sur la fonction. Elle se compose souvent :

  • d'une courte phrase explicative
  • d'une description des attendus sur les paramètres (type et contenu voulu)
  • d'une description de ce que va renvoyer la fonction

01° Utiliser le programme suivant dans Thonny pour vous convaincre que cela fonctionne.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
def convertir_sur_10(note) : '''Renvoie une note sur 10 à partir d'une note sur 20 :param note: nombre dans [ 0 ; 20 ] :type note: int or float :return: la note convertie sur 10 , dans [ 0 ; 10 ] :rtype: int or float .. note:: le retour a le même type que le paramètre note ''' reponse = note // 2 return reponse print(convertir_sur_10(20)) print(convertir_sur_10(10)) print(convertir_sur_10(5))

En utilisant au besoin le mode pas à pas de Thonny, pour visualiser le contenu de la variable note au fur et à mesure de l'exécution du programme :

  • Lors de l'exécution de la ligne 16, que contient le paramètre note ?
  • Lors de l'exécution de la ligne 17, que contient le paramètre note ?
  • Lors de l'exécution de la ligne 18, que contient le paramètre note ?

...CORRECTION...

Ligne 16 :

  • L'appel de la fonction est convertir_sur_10(20)
  • L'exécution de la fonction se fait donc comme si on avait tapé : note = 20

Ligne 17 :

  • L'appel de la fonction est convertir_sur_10(10)
  • L'exécution de la fonction se fait donc comme si on avait tapé : note = 10

Ligne 18 :

  • L'appel de la fonction est convertir_sur_10(5)
  • L'exécution de la fonction se fait donc comme si on avait tapé : note = 5

Voici un exemple de déroulé avec une documentation plus personnelle : encore une fois, cela ne change rien au programme en lui-même :

CLIQUEZ ICI POUR VOIR LE CONTENU DES VARIABLES :

convertir_sur_10 :

note :

reponse :

1 2 3 4 5 6 7 8 9 10 11 12 13
def convertir_sur_10(note) : '''Renvoie une note sur 10 à partir d'une note sur 20 :: param note (int/float) :: entre 0 et 20 inclus :: return (int/float) :: entier, entre 0 et 10 inclus ''' reponse = note // 2 return reponse print(convertir_sur_10(20)) print(convertir_sur_10(10)) print(convertir_sur_10(5))
Spécifications - Préconditions - Postconditions

Dans la documentation précédente, les explications sur les paramètres ou le retour sont des spécifications.

Comme il s'agit d'une procédure déjà concue, il faut le voir de cette manière :

Les spécification d'entrée sont les conditions que vous devez respecter sur les paramètres lorsque vous faites appel à la fonction. Ici, :: param note (int/float) :: entre 0 et 20 inclus veut dire que vous devez envoyer un tel paramètre, sous peine de provoquer éventuellement une erreur. La personne ayant codé la fonction prévient ainsi qu'il n'a pas placé de filtre. 20.0 c'est ok. 21 ou 20.1 non.

Ces spécification d'entrée sont également nommés des preconditions.

Les spécification de sortie (le return) sont des certitudes que l'utilisateur peut avoir sur la nature de ce que renvoie la fonction ou la procédure. Sous condition que les paramètres d'entrée envoyés soient corrects ! :: return (int/float) :: entier, entre 0 et 10 inclus veut donc dire qu'on peut avoir la certitude que la réponse de la fonction est un integer compris entre 0 et 10. Pourquoi noter nombre entier et pas int ? Simplement car 5.0 // 2 renvoie 2.0, un nombre entier mais encodé comme un float, pas comme un int.

Ces spécification de sortie sont également nommés des postconditions.

Dans la norme RST, on sépare l'explication du paramètre et son type, comme avec le return :

:param note: nombre dans [ 0 ; 20 ] :type note: int or float

Ma version personnelle est plus concise mais ne sera pas comprise lors d'une génération automatique de documentation :

:: param note (int/float) :: entre 0 et 20 inclus

Bien entendu, on peut également réaliser des procédures avec des paramètres :

1 2 3 4 5 6 7 8 9 10 11 12 13
def presentation(nom, prenom, profession) : '''Affiche un texte formaté présentant le personnage :: param nom (str) :: le nom du personnage :: param prenom (str) :: le prenom :: param profession (str) :: la description de la profession :: return (None):: (c'est une procédure) ''' print(f"Nom\t{nom}\nPre.\t{prenom}\nPro.\t{profession}") presentation("Skywalker", "Luke", "Jedi sur le retour") presentation("Yoda", "Juste Yoda", "Maitre Jedi tout vert")

✎ 02° Tester la procédure ci-dessus et changer les valeurs fournies lors des appels lignes 12 et 13 pour voir si vous avez bien compris le principe. Quelle doit être la nature des paramètres d'après le concepteur de la procédure (voir la documentation) ?

03° Transformer cette procédure en fonction en tenant compte des spécifications d'entrée signalées et en respectant les spécifications de sortie.

1 2 3 4 5 6 7 8 9 10 11 12 13 14
def presentation(nom, prenom, profession) : '''retourne un texte formaté présentant le personnage :: param nom(str) :: le nom du personnage :: param prenom(str) :: le prenom :: param profession(str) :: décrit la profession :: return (str) :: contient la description créée ''' ???(f"Nom\t{nom}\nPre.\t{prenom}\nPro.\t{profession}") descLuke = presentation("Skywalker", "Luke", "Jedi sur le retour") print(descLuke) print(presentation("Yoda", "Juste Yoda", "Maitre Jedi tout vert"))

...CORRECTION...

1 2 3 4 5 6 7 8 9 10 11 12 13 14
def presentation(nom, prenom, profession) : '''retourne un texte formaté présentant le personnage :: param nom(str) :: le nom du personnage :: param prenom(str) :: le prenom :: param profession(str) :: décrit la profession :: return (str) :: contient la description créée ''' return(f"Nom\t{nom}\nPre.\t{prenom}\nPro.\t{profession}") descLuke = presentation("Skywalker", "Luke", "Jedi sur le retour") print(descLuke) print(presentation("Yoda", "Juste Yoda", "Maitre Jedi tout vert"))

04° Compléter le prototype (la ligne def) de la fonction note_valide pour qu'elle fonctionne correctement. Pour l'instant, elle ne possède aucun paramètre.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
def note_valide() : '''Renvoie True si note est dans l'intervalle [ 0 ; 20 ] :: param note (int/float) :: la note à tester :: return (bool):: True si note dans [ 0 ; 20 ] ''' if note > 20 : return False elif note < 0 : return False else : return True reponse = note_valide(5) print(reponse)

...CORRECTION...

Il suffit de compléter l'en-tête de la fonction. Vous auriez pu vous en rendre compte en exécutant le code fourni sans le modifier. On obtient alors :

Traceback (most recent call last): File "/home/rv/test5.py", line 18, in reponse = note_valide(5) TypeError: note_valide() takes 0 positional arguments but 1 was given

Il suffit donc d'écrire ceci :

def note_valide(note) :

Après cette partie, on voit donc que la fonction informatique se rapproche fortement de la fonction mathématique :

  • On fournit des données d'entrée (soumises à des preconditions ou spécifications d'entrée) à la fonction (contenues dans les paramètres de la fonction informatique)
  • auxquelles on applique un certain nombre d'instructions qui permettent
  • de déterminer une sortie (qu'on transmet via le return de la fonction informatique) soumise à des postconditions ou spécifications de sortie.
  • représentation d'une fonction

2 - Arguments et paramètres

Vocabulaire : argument et paramètre

Arguments et paramètres caractérisent simplement :

  • ce qu'on envoie à la fonction : les arguments
  • ce qui est utilisé pour stocker les informations reçues : les paramètres

Voici un exemple :

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
def note_valide(note) : '''Renvoie True si note est dans l'intervalle [ 0 ; 20 ] :: param note (int/float) :: la note à tester :: return (bool):: True si note dans [ 0 ; 20 ] ''' if note > 20 : return False elif note < 0 : return False else : return True reponse = note_valide(5) print(reponse)

Dans ce cas :

  • 5 est un argument : c'est ce qu'on envoie à la fonction
  • note est un paramètre : on l'utilise pour stocker une copie de l'argument.

05° Analyser le programme suivant sans le lancer. Répondre aux questions puis vérifier vos réponses en lançant directement le code.

  • 5.1 Que contient note-du-programme à la ligne 5 ?
  • 5.2 Que contient l'argument envoyé à la fonction lors de la ligne 6 ?
  • 5.3 Que contient alors note-de-la-fonction à la ligne 1 ?
  • 5.4 Que contient note-de-la-fonction à la ligne 2 ?
  • 5.5 Quelle est l'information renvoyée par la fonction sur la ligne 3 ?
  • 5.6 Que contient alors note2-du-programme une fois revenu sur la ligne 6 ?
  • 5.7 Qu'affiche la ligne 7 ?
  • 5.8 Qu'affiche la ligne 8 ?
1 2 3 4 5 6 7 8
def fois_deux(note) : note = note * 2 return note note = 10 note2 = fois_deux(note) print(note) print(note2)

...CORRECTION...

  • 5.1 Que contient note-du-programme à la ligne 5 ? 10

  • 5.2 Que contient l'argument envoyé à la fonction lors de la ligne 6 ? On transmet le contenu de note donc 10

  • 5.3 Que contient alors note-de-la-fonction à la ligne 1 ? 10

  • 5.4 Que contient note-de-la-fonction à la ligne 2 ? 20 mais attention, il y a eu affection : il s'agit d'une nouvelle variable locale qui a écrasé l'ancienne. L'ID de la variable note-de-la-fonction ligne 2 ne correspond pas à l'ID de la variable note-de-la-fonction ligne 1

  • 5.5 Qu'elle est l'information renvoyée par la fonction sur la ligne 3 ? 20

  • 5.6 Que contient alors note2-du-programme une fois revenu sur la ligne 6 ? 20

  • 5.7 Qu'affiche la ligne 7 ? 10 ! En effet, la variable globale note-du-programme n'est pas la variable locale note-de-la-fonction, même si elles portent le même nom. L'espace des noms permet de faire la différence.

  • 5.8 Qu'affiche la ligne 8 ? 20.

3 - Portée des variables globales

Nous avons donc vu les variables locales. Il reste à voir les variables globales.

Variables globales

On nomme variables globales toutes les variables définies en dehors des fonctions. Elles sont donc directement définies dans le programme principal.

06° Utilisez le programme ci-dessous dans Thonny (en mode DEBUG en utilisant Step Into pour aller dans la procédure) ou simplement Python Tutor. Ce programme permet d'afficher le contenu de quelques variables et surtout affiche l'identifiant mémoire de la variable ennemi (en ne gardant que le reste après division par 1000 pour avoir un affichage plus lisible). Répondre aux questions suivantes  :

  • la variable ennemi est-elle déclarée et affectée (avec un  = ) dans la procédure ?
  • la variable ennemi est-elle globale ou locale du coup ?
  • une variable globale (définie dans le corps du programme) peut-elle être lue dans une fonction ?
1 2 3 4 5 6 7 8 9 10 11 12 13
# PARTIE 1 - Variables globales définies dès le départ ennemi = 'Darth Vador' # PARTIE 2 - Déclaration des fonctions def presentation(nom) : print(f"\n{nom} vs {ennemi}") print(f"Dans la fonction : ennemi a comme id {id(ennemi)%1000}") # PARTIE 3 - Programme principal print(f"Dans le corps du programme : ennemi a comme id {id(ennemi)%1000}") presentation("Yoda") presentation("Luke")

...CORRECTION...

On voit clairement que la variable ennemi-du-programme-principal est définie en dehors des fonctions. On ne trouve aucune affectation d'une variable portant ce nom dans les fonctions : nulle part dans les fonctions n'apparait ennemi = ...

Il s'agit donc d'une variable globale.


Le programme pourrait afficher ceci :

Dans le corps du programme : ennemi a comme id 520 Yoda vs Darth Vador Dans la fonction : ennemi a comme id 520 Skywalker vs Darth Vador Dans la fonction : ennemi a comme id 520

On voit donc que les fonctions et les procédures peuvent accéder et lire le contenu associé à une variable globale dans Python. En effet, depuis les fonctions, on lit bien le contenu associé à l'id de ennemi. 520 ici.

On voit donc que les fonctions peuvent accéder aux variables globales.

Cela veut dire que les fonctions parviennent à accéder à ennemi-du-programme-principal.

Mais peuvent-elles les modifier ?

07° Modifier le code de la procédure presentation comme proposé (rajout de la ligne 7, en jaune).
Lancer le code en mode pas à pas (dans Thonny ou Python Tutor) et répondre aux questions suivantes :

  • Quel est l'id de ennemi-du-programme-principal dans le corps du programme ?
  • Dans presentation, quel est l'id de cette variable ? Devrait-on la nommer ennemi-du-programme-principal ou ennemi-de-presentation ?
1 2 3 4 5 6 7 8 9 10 11 12 13 14
# PARTIE 1 - Variables globales définies dès le départ ennemi = 'Darth Vador' # PARTIE 2 - Déclaration des fonctions def presentation(nom) : ennemi = "Sénateur Palpatine" print(f"\n{nom} vs {ennemi}") print(f"Dans la fonction : ennemi a comme id {id(ennemi)%1000}") # PARTIE 3 - Programme principal print(f"Dans le corps du programme : ennemi a comme id {id(ennemi)%1000}") presentation("Yoda") print(f"Dans le corps du programme, ennemi reste {ennemi}")

...CORRECTION...

On peut croire que la variable ennemi est modifiée dans la fonction. Mais non !

Le programme pourrait afficher ceci :

Dans le corps du programme : ennemi a comme id 024 Yoda vs Sénateur Palpatine Dans la fonction : ennemi a comme id 976 Dans le corps du programme, ennemi reste Darth Vador

Il suffit de regarder les id pour voir ce qui se passe.

  • Dans le programme principal : ennemi_du_programme_principal a comme id 024.
  • Dans la fonction presentation : ennemi-de-presentation a comme id 976 : il ne s'agit donc pas de la même variable que la précédente : on fait référence à une autre zone mémoire.
  • Lorsqu'on revient dans le programme, on affiche Darth Vador puisqu'il s'agit de la variable ennemi_du_programme_principal

08° Alors, la fonction presentation a-t-elle réussi à modifier réellement la variable ennemi-du-programme-principal ?

...CORRECTION...

Non, la ligne 8, ennemi = "Sénateur Palpatine", ne permet que de créer une variable locale ennemi-presentation portant le même nom que la variable globale ennemi-du-programme-principal. Mais leurs ids sont différents : il s'agit donc bien de variables différentes.

09° La variable ennemi-presentation-yoda existe-elle encore à la ligne 20  ?

...CORRECTION...

Non. Il s'agit d'une variable locale à la fonction. La référence est donc détruire lorsqu'on sort de la fonction.

Deux dernières questions pour voir si vous avez bien compris cette partie assez délicate :

10° Que vont afficher les lignes du programme suivant ? Donnez les réponses dans l'ordre d'exécution !

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
# - - - Variables globales définies dès le départ ennemi = 'Darth Vador' # - - -Déclaration des fonctions - - - - def presentation_yoda() : nom = "Yoda" print(ennemi) ennemi = "Sénateur Palpatine" print(ennemi) def presentation_luke() : nom = "Skywalker" print(ennemi) # - - - Programme principal - - - - - - - - - print(ennemi) presentation_yoda() presentation_luke()

...CORRECTION...

Ligne 17 : la variable ennemi-programme-principal contient 'Darth Vador'.

Ligne 08 : la variable ennemi-presentation-yoda n'existe pas. L'interpréteur va donc chercher dans l'espace des noms pour trouver une variable globale portant le nom ennemi-programme-principal. Il trouve. Le print va donc afficher 'Darth Vador'.

Ligne 10 : une variable ennemi-presentation-yoda a été déclarée juste au dessus. Cette fois, l'espace des noms de la fonction trouve donc une référence locale. Le print va donc afficher "Sénateur Palpatine".

Ligne 14 : la variable ennemi-presentation-luke n'existe pas. L'interpréteur va donc chercher dans l'espace des noms pour trouver une variable globale portant le nom ennemi-programme-principal. Il trouve. Le print va donc afficher 'Darth Vador'.

Portée des variables globales

Si on résume la portée des variables globales en image, ça donne ceci :

portee-variable-globale

La fonction peut lire la variable globale tant qu'on ne tente pas d'y faire une affectation sur une variable portant le même nom (avec un =).

Si la fonction utilise une affectation sur ce nom de variable, la fonction crée simplement une variable locale portant le même nom que la variable globale.

Une fonction ne peut donc pas modifier une variable globale.

4 - Mot-clé Global

Comment faire alors pour modifier la variable globale ennemi-programme-principal depuis une fonction ? Cela pourrait être sympathique de pouvoir changer l'ennemi en fonction de la saga non ?

Voyons comment avec ce petit bout de cours :

Rappel : comportement de Python par défaut
  • une fonction peut lire une variable globale.
  • une fonction ne peut pas modifier une variable globale
    • l'utilisation d'une affectation (=) crée simplement une variable locale portant le même nom.
Nouveau : modification du comportement par défaut
  • une fonction peut modifier une variable globale si on lui en donne l'autorisation :
    • en utilisant le mot-clé global suivi du nom de la variable avant de l'affecter

Mise en pratique immédiate :

1 2 3 4 5 6 7 8 9
ennemi = "Darth Vador"
def test(): global ennemi ennemi = "Palpatine"
print(ennemi) test() print(ennemi)

Ligne 7 : On affiche le contenu "Darth Vador"

Lignes 8 puis 4 et 5 : On permet à la fonction de modifier la variable en "Palpatine"

Ligne 9 : La variable globale contient bien "Palpatine" maintenant.

11° Lancer ce programme sur Python Tutor ou Thonny en mode DEBUG pour visualiser la modification du contenu de la variable.

12° Utiliser maintenant Thonny en mode DEBUG avec visualisation du tas (heap) pour connaitre le contenu ET l'id de la variable au cours de l'execution du programme. L'id de la variable globale a-t-elle été modifié après la ligne 5 ? Pour vous convaincre de la différence sans la ligne 4, vous pouvez la supprimer et relancer un nouveau debug.

CLIQUEZ ICI POUR VOIR LE CONTENU DES VARIABLES :

test :

ennemi :


Id 0x6E18 :

Id 0x3FC0 :

Id 0xB030 :

1 2 3 4 5 6 7 8 9
ennemi = "Darth Vador"
def test(): global ennemi ennemi = "Palpatine"
print(ennemi) test() print(ennemi)

Voilà, nous sommes parvenu à modifier une variable globale depuis une fonction. Attention, n'oubliez pas que la variable change d'identifiant associé, comme à chaque fois qu'on utilise une affectation  = .

Spécificités des portées en fonction des variables

La portée des variables globales et locales, ainsi que la façon dont on peut les modifier est variable en fonction du langage de programmation. Il s'agit d'une des premières choses qu'il faut regarder et comprendre lorsqu'on s'attaque à un nouveau langage. Pour vous, c'est fait avec Python maintenant.

Note finale : Limitez l'utilisation des variables globales !

La modification des variables globales avec global doit rester marginale dans vos programmes. Il est en effet difficile de maintenir fonctionnel un code qui comporte plusieurs variables modifiées par plusieurs fonctions créées par des personnes différentes.

Si vous devez utilisez des variables globales (même en lecture simple), respectez au moins ces trois points :

  1. Placer les clairement en début de code dans la partie adéquate de façon à clairement les mettre en évidence.
  2. Placer des commentaires pour expliquer à quoi elles servent
  3. Donner leur des noms suffisamment rares pour que quelqu'un ne les écrase dans une fonction par une variable locale portant le même nom : si l'une de vos variables globales se nomme x, il ne faudra pas vous plaindre si l'une des personnes de votre équipe utilise aussi ce nom dans l'une de ses fonctions !

Sachez que nous verrons plus tard des outils nous permettant de nous passer des modifications de variables globales depuis les fonctions. Vous pouvez donc les utiliser pour l'instant, mais gardez à l'esprit qu'il s'agit plutôt d'une mauvaise pratique.

Nous allons encore en découvrir beaucoup sur les fonctions.

Si on récapitule, nous avons vu aujourd'hui :

  • la différence entre fonction (avec retour) et procédure (sans retour)
  • les paramètres
  • la différence entre arguments et paramètres

Dans l'activité suivante, nous allons voir comment travailler en équipe sur un projet utilisant les fonctions (c'est le cas de 100% des projets) :

  • créer une documentation
  • les doctest visant à fournir de la documentation ET à tester les fonctions
  • la structuration d'un programme complexe en multiples petites fonctions

Activité publiée le 28 08 2019
Dernière modification : 19 07 2020
Auteur : ows. h.