fonctions python

Identification

Infoforall

9 - Fonctions et variables


Vous avez vu qu'il s'agit de sortes de boites noires à qui on fournit des ENTREES et que nous répondent en retour.

ENTREE(S)  ⇒   Fonction   ⇒  SORTIE

Vous savez actuellement faire appel à ces fonctions. Nous allons voir aujourd'hui comment en créer vos propres fonctions, qui ne seront donc pas des fonctions natives, présentes de base dans Python.

Commençons par vous montrer ce qu'on peut faire assez facilement avec les fonctions.

animation d'introduction

Téléchargez le code à l'aide de l'image et lancez le. Nous obtenons une animation.

Sans fonction, difficile de réaliser ceci...

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

Evaluation: 7 questions

  07-11-12-13-14-19

  20

DM 🏠 : Non

Evaluation ✎ : question 07-08-10-13-14-18-19-20

Documents de cours PDF : .PDF

Sources latex : .TEX et entete.tex et licence.tex

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

1 - Fonctions, paramètres, arguments

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 :

  • recoivent 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.

>>> max(10, 0, 50, 40) 50
(Rappel) 1.3 FONCTION : personnelle
1 2 3 4
def double(x): # Déclaration return 2 * x # Déclaration n = double(12) # Appel

On distingue deux choses :

  1. La déclaration (mise en mémoire d'instructions pour les utiliser plus tard)
  2. 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

  1. le mot-clé def.
  2. le nom de la fonction
  3. entre parenthèses : les variables d'entrée séparées par des virgules
  4. le caractère :
  5. les instructions du bloc tabulé
1 2
def double(x): return 2 * 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 developpeurs 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.

Sur l'exemple, l'appel se fait LIGNE 4.

1 2 3 4
def double(x): # Déclaration return 2 * x # Déclaration n = double(12) # Appel

Déroulé

  • L1 (déclaration)
  • L4 (appel) - L1 - L2 (envoi) - L4 (réception de la réponse)

Traduction (et déroulé)

  • Ligne 1 : déclaration d'une fonction double() qui va recevoir une donnée qu'on placera dans une variable x.
  • Ligne 4 (appel à la fonction double() en lui envoyant 12)
  • Ligne 1 : l'interpréteur place 12 dans x.
  • Ligne 2 : la fonction envoie 24 à la ligne qui a lancé l'appel
  • Ligne 4 (réception de la réponse qu'on stocke dans une variable n.
(Rappel) 1.4 FONCTION : plus complexe
1 2 3 4 5 6 7
def nouvelle_note(note): note = note + 5 if note > 20: note = 20 return note n = nouvelle_note(12)

Cette fonction récupère la valeur d'une note, rajoute 5, bloque la note à 20 si elle dépassait 20 et renvoie le résultat final.

Déroulé

  • L1 (déclaration)
  • L7 (appel) - L1 - L2 - L3 - L5 (envoi) - L7 (réception)

Traduction et déroulé

  • 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 etant alors faux (17 n'est pas supérieur à 20), on passe en ligne 5 : la fonction répond 17
  • 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
def ma_fonction(x): resultat = x * 2 return resultat

Voici comment déclarer une fonction :

  1. 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.
  2. 1
    def fois2
  3. on rajoute des parenthèses,
  4. 1
    def fois2()
  5. 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).
  6. 1
    def fois2(x)
  7. 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.
  8. 1
    def fois2(x):
  9. 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).
  10. 1 2
    def fois2(x): resultat = x * 2
  11. On finit par return suivi de la réponse qu'on veut renvoyer, ici resultat
  12. 1 2 3
    def fois2(x): resultat = x * 2 return resultat
J'insiste mais... "Déclarer" n'est pas "Faire appel"

Il faut comprendre qu'après avoir exécuter 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 :

  1. Il évalue l'expression située derrière lui.
  2. Il renvoie la valeur trouvée à l'entité qui a 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
    def fois2(x): resultat = x * 2 return resultat
  • Placer directement l'expression derrière return.
  • 1 2
    def fois2(x): return x * 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.

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 

  1. Ouvrir l'onglet VARIABLES de Thonny pour vérifier que la mémoire est vide initialement
  2. Sauvegarder et lancer le programme suivant sous le nom activite_fonctions.
  3. 1 2 3 4 5 6 7
    def fois2(x): resultat = x * 2 return resultat def plus2(x): resultat = x + 2 return resultat
  4. Vérifier via l'onglet VARIABLES que la mémoire contient maintenant deux nouvelles variables nommées fois2 et plus2.
  5. Le at0x... 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 :

    résultat dans thonny

  6. Utiliser ensuite la console pour visualiser que fois2 (juste le nom, sans les parenthèses) ne lance aucun appel mais spécifie que ce nom existe et fait référence à une variable de type function puisqu'elle fait référence à des instructions.
  7. >>> fois2 <function fois2 at 0x7f2d96628af0> >>> type(fois2) <class 'function'>

Rajoutons maintenant une couche de vocabulaire à connaître.

1.7 FONCTION : paramètre et argument
1 2 3
def valeur(d, u): # d et u sont les paramètres déclarés resultat = d*10 + u return resultat
>>> valeur(5, 3) # 5 et 3 sont les Arguments d'Appel 53

Différence entre paramètre et argument

Il faut savoir distinguer

  • 1a 1er ligne de la déclaration qui contient les variables locales de stockage des 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
    def valeur(d, u): # d et u sont les paramètres déclarés resultat = d*10 + u return resultat

    Ces paramètres dispaissent donc une fois que la fonction a répondu.

  • son appel qui indique 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
def fois2(x): <-- x est le paramètre de déclaration
>>> fois2(20) <-- 20 est l'argument de l'appel 40

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
def valeur(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
def valeur(d, u): # d et u sont les paramètres déclarés resultat = d*10 + u return resultat valeur(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.

02° Les fonctions sont maintenant en mémoire. 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) ???
  1. Quel est l'argument envoyé à la fonction plus2() lors de cet appel ?
  2. Quel est alors le contenu affecté au paramètre x lors de cet appel ?
  3. Que va contenir resulat lors de cet appel ?
  4. Quelle est la réponse renvoyée par cet appel de fonction (qui s'affiche puisqu'on en fait rien et que nous travaillons dans la console) ?
>>> plus2(15) ???
  1. Quel est l'argument envoyé à la fonction plus2() lors de cet appel ?
  2. Quel est alors le contenu affecté au paramètre x lors de cet appel ?
  3. Que va contenir resulat lors de cet appel ?
  4. Quelle est la réponse renvoyée par cet appel de fonction (qui s'affiche puisqu'on en fait rien et que nous travaillons dans la console) ?
>>> fois2(10) ???
  1. Quel est l'argument envoyé à la fonction fois2() lors de cet appel ?
  2. Quel est alors le contenu affecté au paramètre x lors de cet appel ?
  3. Que va contenir resulat lors de cet appel ?
  4. Quelle est la réponse renvoyée par cet appel de fonction (qui s'affiche puisqu'on en fait rien et que nous travaillons dans la console) ?

...CORRECTION...

>>> plus2(10) 12
  1. Ici, on voit que l'entrée est 10.
  2. Le paramètre x référence l'entier 10.
  3. La variable resultat référencera 12 après exécution de la ligne 6.
  4. Cet appel renvoie 6.
>>> plus2(15) 17
  1. Cette fois, on envoie 15.
  2. x référence 15.
  3. resulat référence donc 17.
  4. Cet appel renvoie 17.

>>> fois2(10) 20
  1. On envoie une entrée valant 10.
  2. x référence 10.
  3. resultat référence alors 20 (voir ligne 2).
  4. Cet appel 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)
  1. Quel est l'argument envoyé à la fonction lors de cet appel ?
  2. Quel est le contenu affecté au paramètre x lors de cet appel ?
  3. Quelle est la réponse renvoyée par cet appel de fonction ?
  4. Pourquoi n'obtient-on pas d'affichage dans la console ?
>>> b = fois2(a)
  1. Quel est l'argument envoyé à la fonction lors de cet appel ?
  2. Quel est le contenu affecté au paramètre x lors de cet appel ?
  3. Quelle est la réponse renvoyée par cet appel de fonction ?
  4. Pourquoi n'obtient-on pas d'affichage dans la console ?
>>> c = fois2(plus2(10))
  1. Que va évaluer d'abord l'interpréteur Python à cause des priorités ?
  2. Quel est l'argument envoyé à la fonction fois2() lors de cet appel ?
  3. Expliquer le contenu de c.

...CORRECTION...

>>> a = plus2(100)
  1. L'argument envoyé est 100.
  2. x contient donc 100.
  3. La fonction répond donc 102 qu'on affecte à la variable a.
  4. 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)
  1. L'argument envoyé est a.
  2. x référence donc 102.
  3. La fonction répond donc 204 qu'on affecte à la variable b.
  4. 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))
  1. En respectant la priorité des parenthèses, on se rend comppte qu'il faut d'abord évaluer plus2(10).
  2. Python va évaluer plus2(10) à la valeur 12. On envoie donc ce 12 comme argument à fois2().
  3. 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 :

    >>> fois2() ???
  1. Quel est le type d'erreur ?
  2. A quoi est due cette erreur ?
  3. >>> fois2(10, 20) ???
  4. Quel est le type d'erreur ?
  5. A quoi est due cette erreur ?
  6. >>> fois2 10 ???
  7. Quel est le type d'erreur ?
  8. A quoi est due cette erreur ?
  9. >>> fois2([10]) ???
  10. Quel est le type d'erreur ?
  11. A quoi est due cette erreur ?

...CORRECTION...

    >>> fois2() TypeError: fois2() missing 1 required positional argument: 'x'
  1. C'est une erreur TypeError. En Python, il s'agit d'une erreur d'exécution.
  2. 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.
  3. >>> fois2(10, 20) TypeError: fois2() takes 1 positional argument but 2 were given
  4. C'est une erreur TypeError. En Python, il s'agit d'une erreur d'exécution.
  5. On envoie 2 arguments lors de l'appel alors que la fonction possède UN paramètre.
  6. >>> fois2 10 SyntaxError: invalid syntax
  7. C'est une erreur de SYNTAXE. Python ne comprend pas l'appel.
  8. L'utilisateur a oublié les parenthèses nécessaires en Python lors de l'appel.
  9. >>> fois2([10]) [10, 10]
  10. Par d'erreur en soi : pas d'erreur de syntaxe, ni d'erreur d'exécution.
  11. 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.

  12. 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° Aller sur Python Tutor, et visualiser le déroulé du programme suivant en mode pas à pas.

1 2 3 4 5 6 7
def valeur(d, u): resultat = d*10 + u return resultat a = valeur(2, 3) b = valeur(5, 7) c = valeur(9, 0)

Questions une fois avoir vu le déroulé par à pas

  1. Lorsqu'on exécute la première fois le bloc de déclaration L1-L2-L3, met-on la fonction en mémoire ou lance-t-on la fonction ?
  2. Chaque appel de fonction dispose-t-il de son propre espace des noms local ou place-t-on les arguments dans l'espace des noms global ?
  3. Que devient un espace des noms local après que la fonction ai répondu ?
  4. Les variables d, u et resultat sont-elles des variables locales ou globales ?
  5. Les variables a, b et c sont-elles des variables locales ou globales ?

...CORRECTION...

  1. On place juste la fonction en mémoire. C'est une déclaration, pas un appel.
  2. 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.

  3. Dès qu'une fonction a répondu, l'espace des noms local de cet appel est supprimé puisqu'il ne sert plus à rien.
  4. Les variables d, u et resultat sont des variables locales puisqu'elles appartiennent à l'espace qui disparait.
  5. 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
def valeur(d, u): resultat = d*10 + u return resultat
>>> 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
def valeur(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
def facile(x): return x*10 + 5
>>> facile(3) ???

Deuxième fonction

1 2
def plus_difficile(x, y, z): return x*100 + y*10 + z
>>> plus_difficile(5, 6, 3) ???

08° Pour valider cette première partie :

  1. Appuyer sur le bouton VISUALISER ci-dessous, pour observer l'ordre d'exécution de ce programme
  2. Expliquer les valeurs finales stockées dans les variables a et b après exécution du code ci-dessous.
  3. Vérifiez ensuite en lançant le code réellement.
  4. Modifier vos réponses au besoin !
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
# 3 - - Déclaration des fonctions - - - - def fois2(x): resultat = x * 2 return resultat def plus2(x): resultat = x + 2 return resultat # 4 - - Programme principal - - - - - nombre = 3 nombre2 = 5 a = plus2(nombre) b = fois2(a) b = b + 1

CLIQUEZ ICI POUR VOIR L'ORDRE DES INSTRUCTIONS EXECUTEES :

Pour visualiser les valeurs des deux variables, deux possibiltés :

  • Soit vous utilisez votre éditeur de code (par exemple VIEW-VARIABLES dans Thonny)
  • Soit vous demandez les résultats via la console interactive :
  • >>> a >>> b

...CORRECTION...

On obtient ceci

  • Mise en mémoire de la fonction fois2()
  • Mise en mémoire de la fonction plus2()
  • Affectation de 3 dans nombre
  • Affectation de 5 dans nombre2
  • plus2(nombre) renvoie 5.
  • Affectation de 5 dans a
  • fois2(a) renvoie 10 car a est évaluée à 5.
  • Affectation du 10 dans b
  • Evaluation de b + 1 à la valeur 11
  • Affectation du 11 dans b

2 - Documentation : expliquer comment utiliser

Le but de l'informatique ?

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 commentaire print("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

  1. Le but de cette fonction
  2. le type et le nombre d'arguments à lui envoyer (les ENTREES) et
  3. 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

  1. Dans la documentation, où est-il indiqué que bin() est une fonction native ?
  2. combien d'argument(s) doit-on envoyer à bin() ? De quel(s) type(s) ?
  3. En traduisant la pĥrase de description, expliquer ce que renvoie la fonction (le type et le contenu de la réponse).

...CORRECTION...

  1. Il est noté que bin() est une fonction build-in, "construire à l'interne" en traduction mot à omt.
  2. 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.
  3. 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
def addition(nbr1:int, nbr2:int) -> int: """Renvoie la somme de nbr1 et nbr2""" return nbr1 + 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 :

  1. de savoir comment elle se nomme.
  2. de savoir combien d'arguments lui envoyer et leurs types.
  3. 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 :

  1. Le PROTOTYPE qui fixe la SYNTAXE et le TYPAGE
  2. 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
def addition(nbr1:int, nbr2:int) -> int: """Renvoie la somme de nbr1 et nbr2""" return nbr1 + nbr2
2.3.4 - Obtenir la documentation

La fonction help() ramene 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
def calculer_moyenne(note1:int, note2:int) -> float: """Renvoie la moyenne des deux notes envoyées""" return 0.0 m = 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 !)

>>> calculer_moyenne(10, 20) 15.0 >>> calculer_moyenne(10, 14) 12.0

...CORRECTION...

1 2 3 4 5
def calculer_moyenne(note1:int, note2:int) -> float: """Renvoie la moyenne des deux notes envoyées""" return (note1 + note2) / 2 m = 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
def vitesse(distance:int, duree:int) -> float: """Renvoie la vitesse connaissant la distance parcourue et la durée""" return distance / duree
  1. Problème 1 : on ne signale pas à l'utilsateur qu'il ne doit surtout pas fournir de durée nulle pour éviter une division par zéro.
  2. 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
def vitesse(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 """ return distance / duree

10-B° Lire la nouvelle documentation de calculer_moyenne(). Répondre aux questions suivantes :

  1. Sur quelles lignes trouve-t-on les types attendus des deux paramètres ?
  2. Quelles sont les préconditions qu'on trouve dans cette documentation plus longue ?
  3. 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
def 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 """ return (note1 + note2) / 2 moy = calculer_moyenne(10, 20)

Enfin, lancer le programme dans Thonny pour vérifier que la fonction fonctionne de la même façon que celui de 10-1 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.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
def addition(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 """ return 0 def multiplication(x, y): """Renvoie la multiplication de a par b :: param x(int) :: un entier :: param y(int) :: un entier :: return (int) :: x*y """ return 0 def division_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)
>>> addition(12, 14) 26 >>> resultat = addition(10, 14) >>> resultat 24 >>> multiplication(5, 10) 50 >>> resultat = multiplication(6, 6) >>> resultat 36 >>> division_euclidienne(72, 10) (7, 2)

✎ 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
import random def de(nb_faces): '''La petite phrase qui va bien :: à faire : décrire le paramètre :: :: à faire : décrire la réponse :: ''' return random.randint(1, nb_faces)
>>> de(6) 5 >>> de(6) 6 >>> de(6) 3 >>> de(6) 4 >>> de(20) 18 >>> de(20) 3 >>> de(20) 20 >>> de(20) 7
>>> 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.

✎ 13° Réaliser la fonction telle qu'elle est spécifiée dans sa documentation :

1 2 3
def est_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.

✎ 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
def contient(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

3 - Return, ou pas Return, sortez !

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
def centaine(x): """Renvoie le chiffre de la centaine de x :: param x(int) :: un entier :: return (int) :: la centaine de x (5 si 1545) """ return 0 mon_petit_calcul = x // 100 % 10 return mon_petit_calcul

Tester la fonction dans la console.

Questions

  1. Pourquoi cette fonction renvoie-t-elle TOUJOURS 0 ?
  2. 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
def centaine(x): """Renvoie le chiffre de la centaine de x :: param x(int) :: un entier :: return (int) :: la centaine de x (5 si 1545) """ return 0

Après correction, vous devriez avoir ceci :

1 2 3 4 5 6 7 8
def centaine(x): """Renvoie le chiffre de la centaine de x :: param x(int) :: un entier :: return (int) :: la centaine de x (5 si 1545) """ return x // 100 % 10
3.1 FONCTION : sortie immédiate avec return

Dès que la fonction rencontre un return :

  1. elle évalue l'expression fournie puis
  2. elle renvoie sa réponse, puis
  3. 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
def division(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.

Voici la suite :

>>> reponse = division(5, 3) >>> reponse >>> type(reponse) ???
résultat d'une fonction sans retour

Questions

  1. Que contient la variable reponse ?
  2. Quel est le type de cette variable ?

...CORRECTION...

En réalité, la variable contient quelque chose !

Elle contient None.

Quel est le type de None ? NoneType dont la seule valeur possible est None.

>>> reponse = division(5, 3) >>> reponse >>> type(reponse) <class 'NoneType'>
3.2 FONCTION : return None

3.2.1 Le cas du return absent

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
def division(x:int, y:int) -> None: """Calcule la division entière de x par y, mais n'en fait rien...""" mon_petit_calcul = x // y return None
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.

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

4 - Compléments sur les paramètres

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éaoire compris entre un entier debut et un entier fin :

1 2 3 4 5 6 7 8 9 10 11
import random def aleatoire(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] """ return random.randint(debut, fin)

Si cette fonction doit simuler un dé, l'entier debut sera toujour 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
import random def aleatoire(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] """ return random.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
import random def aleatoire(debut:int=1, fin:int=1): """Renvoie un entier aléatoire entre debut et fin""" return random.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
import random def somme_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 somme for _ in range(nb): # Fait l'action nb fois s = s + random.randint(debut, fin) # Incrémente s d'un nombre aléatoire return s
>>> 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.

1 2 3 4
import random def somme_aleatoire(nb, /, debut, fin): ...
  • 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 structurés

Selon les versions de Python, il est possible, ou pas d'écrire ce que contient un type structuré.

Exemple avec une fonction dont le paramètre doit être un tableau d'entiers.

1 2
def f(x:list[int]): ...

Pour un dictionnaire dont les clés sont des strings et les valeurs sont des integers :

1 2
def f(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 :

1 2
def f(x:"dict[(str, int)]"): ...

5 - Fonction et Conteneur Muable

Choisir votre module graphique pour la suite :
(Rappel) 5.1 Principe du programme pyxel

Logo Pyxel
Logo Pyxel

Le principe de fonctionnement d'un programme utilisant le module pyxel est basé sur la séquence suivante qui inclut une boucle infinie :

    1 - La fonction init() crée la fenêtre graphique.

    2 - La fonction run() active l'alterance infinie ci dessous :

    3 - TANT QUE le programme est actif :

      On mémorise les actions de l'utilisateur

      SI la dernière mise à jour date de plus d'un 1/30e de seconde

        On active une fonction controle() qui va

        faire appel à des fonctions maj_...() pour mettre à jour les données

        On modifie l'affichage (la vue) en faisant appel à actualiser_vue() qui va

        faire appel à cls() pour effacer l'écran

        faire appel à des fonctions afficher_...() pour afficher des formes à l'écran

      Fin du SI

    Fin TANT QUE

Principe de Pyxel

17 (pyxel)° Vérifier si le module pyxel est installé :

>>> import pyxel

Si cela ne fonctionne pas, installer la bibliothèque pyxel dans Thonny :

  • Allez dans l'onglet "Outils"
  • Cherchez "Gérer les paquets"
  • Lancez la recherche sur pyxel puis activer l'installation
  • Attendez que l'installation soit totalement effectuée
  • Validez l'installation en utisant le bouton Fermer

Une fois avoir vérifié que le module soit bien installé, placer en mémoire et lancer le programme suivant.

Vérifier que vous parvennez à faire bouger le vaisseau à l'aide des touches flèches gauche et droite.

Questions

  1. Lignes 16 : le dictionnaire data est-il une variable globale ou locale ?
  2. Lignes 29 : le dictionnaire data est-il déclaré comme un paramètre ?
  3. Lignes 32-36 : l'affectation est-elle faite sur le dictionnaire data ou sur la valeur associée à l'une des clés ? En conséquence, data est-elle une variable locale ou globale dans cette fonction ?
  4. Conséquence : nous savons qu'une fonction n'est pas capable de modifier une variable globale, mais une fonction est-elle capable de modifier le CONTENU d'un conteneur global MUABLE ?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
""" Module de présentation des interfaces Homme-machine (ihm) basée sur pyxel On découpe les fonctions en 3 catégories : - celles du CONTROLEUR : la logique liée aux actions de l'utilisateur - celles du MODELE : la gestion des données pures - celles de la VUE : la gestion de ce qu'on montre à l'utilisateur """ # 1 - Importations ============================== import pyxel # 2 - CONSTANTES et variables globales ============================================ data = {'x': 100, 'y': 50} # Dictionnaire stockant les données modifiables # 3 - FONCTIONS ================================================================== def controler_touches(): # CONTROLEUR """Fonction qui récupère l'action, modifie les données en conséquence""" if pyxel.btn(pyxel.KEY_LEFT): actualiser_modele_vaisseau(-1, 0) # Modifie en mémoire la position du vaisseau if pyxel.btn(pyxel.KEY_RIGHT): actualiser_modele_vaisseau(1, 0) # Modifie en mémoire la position du vaisseau def actualiser_modele_vaisseau(dx, dy): # MODELE """Modifie les données du vaisseau pour intégrer le déplacement dx et dy voulu""" data['x'] = data['x'] + dx if data['x'] > 128: data['x'] = 128 if data['x'] < 0: data["x"] = 0 def actualiser_vue(): """création des objets (30 fois par seconde)""" pyxel.cls(0) # vide la fenetre afficher_vaisseau(data['x'], data['y']) # vaisseau (carre 8x8) def afficher_vaisseau(x, y): # VUE """Dessine et mémorise un nouveau vaisseau en (x,y)""" pyxel.rect(x, y, 8, 8, 1) # carré 8x8 et en couleur 1 # 4 - PROGRAMME PRINCIPAL ============================ pyxel.init(128, 128, title="Mon premier jeu") pyxel.run(controler_touches, actualiser_vue)

...Correction...

  1. Lignes 16 : data est une variable globale puisque déclarer dans le programme lui-même.
  2. Lignes 29 : le dictionnaire data ne fait pas partie des paramètres transmis.
  3. Lignes 32-36 : l'affectation est faite sur le contenu du dictionnaire. On ne fait que modifier le contenu du dictionnaire. En conséquence, data est toujours la variable globale.
  4. Conséquence : on constate donc que
    • une fonction n'est pas capable de modifier un conteneur global (on ne peut pas modifier le meuble) mais
    • une fonction peut modifier le contenu d'un conteneur global : si on sait où se trouve une armoire, on peut y aller et modifier son contenu.

18 (pyxel)° Expliquer si la variable data est une variable globale ou locale dans la fonction actualiser_vue() ?

...Correction...

    Le nom data n'apparaît pas en tant que paramètre et il n'y a aucune affectation directe sur data. Il s'agit donc d'une variable globale.

    Dans cette fonction, on utilise le nom de cette variable pour obtenir son adresse puis lire son contenu.

(Rappel) 5.1 Principe du programme tkinter

Le principe de fonctionnement d'un programme utilisant le module tkinter est basé sur la séquence suivante qui inclut une boucle infinie :

    1 - La fonction Tk() crée la fenêtre graphique.

    2 - La méthode bind() permet de faire le lien entre un événement et une fonction de contrôle controle_xxx() de l'événement

    3 - La méthode mainloop() lance la surveillance en boucle des événements

    4 - TANT QUE le programme est actif :

      SI l'utilisateur appuie sur une touche, on active la fonction controle_xxx() correspondante qui va

        D'abord déterminer comment faire appel aux fonctions maj_xxx() pour modifier les modèles

        Ensuite demander à modifier l'affichage (la vue) en faisant appel à actualiser_vue() qui va

        faire appel à delete() pour effacer l'écran

        faire appel à des fonctions afficher_...() pour afficher des formes à l'écran

      Fin du SI

    Fin TANT QUE

Principe de Tkinter

17 (tkinter)° Vérifier si le module tkinter est installé :

>>> import tkinter

Si cela ne fonctionne pas, installer la bibliothèque tkinter dans Thonny :

  • Allez dans l'onglet "Outils"
  • Cherchez "Gérer les paquets"
  • Lancez la recherche sur tkinter puis activer l'installation
  • Attendez que l'installation soit totalement effectuée
  • Validez l'installation en utisant le bouton Fermer
.

Une fois avoir vérifié que le module soit bien installé, placer en mémoire et lancer le programme suivant.

Vérifier que vous parvenez à faire bouger le vaisseau à l'aide des touches flèches gauche et droite.

Questions

  1. Lignes 17 : le dictionnaire data est-il une variable globale ou locale ?
  2. Lignes 34 : le dictionnaire data est-il déclaré comme un paramètre ?
  3. Lignes 37-41 : l'affectation est-elle faite sur le dictionnaire data ou sur la valeur associée à l'une des clés ? En conséquence, data est-il une variable locale ou globale ?
  4. Conséquence : nous savons qu'une fonction n'est pas capable de modifier une variable globale, mais une fonction est-elle capable de modifier le CONTENU d'un conteneur global MUABLE ?
  5. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
    """ Module de présentation des interfaces Homme-machine (ihm) basée sur tkinter On découpe les fonctions en 3 catégories : - celles du CONTROLEUR : la logique liée aux actions de l'utilisateur - celles du MODELE : la gestion des données pures - celles de la VUE : la gestion de ce qu'on montre à l'utilisateur """ # 1 - Importations ============================== from tkinter import Tk # pour créer la fenetre de jeu from tkinter import Canvas # pour créer la zone de dessin # 2 - CONSTANTES et variables globales ============================================ data = {'x': 100, 'y': 50} # Dictionnaire stockant les données modifiables # 3 - FONCTIONS ================================================================== def controler_touches(evenement): # CONTROLEUR """Fonction qui récupère l'action, modifie les données en conséquence puis la vue""" touche = evenement.keysym # touche utilisée (Left, Right, Up, Left) if touche == "Left": # Si l'utilisateur appuie sur FLECHE LEFT actualiser_modele_vaisseau(-3, 0) # Modifie en mémoire la position du vaisseau if touche == "Right": actualiser_modele_vaisseau(3, 0) # Modifie en mémoire la position du vaisseau actualiser_vue() def actualiser_modele_vaisseau(dx, dy): # MODELE """Modifie les données du vaisseau pour intégrer le déplacement dx et dy voulu""" data['x'] = data['x'] + dx if data['x'] > 500: data['x'] = 500 if data['x'] < 0: data["x"] = 0 def actualiser_vue(): # VUE """Efface puis dessine la nouvelle vue""" plateau.delete("all") # Efface les dessins dans le canvas afficher_vaisseau(data['x'], data['y']) def afficher_vaisseau(x, y): # VUE """Dessine et mémorise un nouveau vaisseau en (x,y)""" plateau.create_rectangle(x, y, x+20, y+20, fill="blue", width=0) # 4 - PROGRAMME PRINCIPAL ============================ fenetre = Tk() # Référence de la fenètre graphique fenetre.title("Mon premier jeu") fenetre.geometry("600x600") plateau = Canvas(fenetre) # Référence de la zone du plateau de jeu plateau.configure(width=500) plateau.configure(height=500) plateau.configure(bg="ivory") plateau.place(x=50, y=50) actualiser_vue() # Dessine le vaisseau à la position initiale fenetre.bind("<Any-KeyPress>", controler_touches) # Lie le clavier à la fonction fournie fenetre.mainloop() # Lance la surveillance des événements

...Correction...

  1. Lignes 17 : data est une variable globale puisque déclarer dans le programme lui-même.
  2. Lignes 34 : le dictionnaire data ne fait pas partie des paramètres transmis.
  3. Lignes 37-41 : l'affectation est faite sur le contenu du dictionnaire. On ne fait que modifier le contenu du dictionnaire. En conséquence, data est toujours la variable globale.
  4. Conséquence : on constate donc que
    • une fonction n'est pas capable de modifier un conteneur global (on ne peut pas modifier le meuble) mais
    • une fonction peut modifier le contenu d'un conteneur global : si on sait où se trouve une armoire, on peut y aller et modifier son contenu.

18 (tkinter)° Expliquer si la variable data est une variable globale ou locale dans la fonction actualiser_vue() ?

...Correction...

    Le nom data n'apparaît pas en tant que paramètre et il n'y a aucune affectation directe sur data. Il s'agit donc d'une variable globale.

    Dans cette fonction, on utilise le nom de cette variable pour obtenir son adresse puis lire son contenu.

19 ✎° Compléter maintenant la fonction controler_touches() pour qu'on puisse faire les bons appels en cas d'appui sur les touches UP et DOWN.

Attention, le système ne marchera qu'après la question 20.

20 ✌° Compléter enfin la fonction actualiser_modele_vaisseau() pour qu'on puisse gérer les données liées à la coordonnée y.

5.2 FONCTION : interaction avec une variable globale référençant un type structuré

Une fonction peut ACCEDER à une variable globale.
Une fonction ne peut PAS MODIFIER UNE VARIABLE GLOBALE.
Une fonction peut MODIFIER LE CONTENU d'un CONTENEUR MUABLE GLOBAL

Si la variable globale fait référence à un type structuré, la fonction peut donc accéder à la localisation de la structure. Une fois sur place, elle pourra donc MODIFIER LE CONTENU de la structure, alors qu'elle ne pas peut modifier la structure.

6 - FAQ

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

C'est tout en terme de connaissances sur les fonctions pour aujourd'hui. Si on récapitule, nous avons vu :

  • Comment déclarer une fonction
  • Comment documenter un peu les fonctions
  • Comment une fonction parvient à renvoyer quelque chose avec le mot-clé return
  • Qu'on sort IMMEDIATEMENT de la fonction dès qu'on rencontre return
  • Qu'une fonction-python renvoie au moins None.

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