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 déclarer des fonctions, faire appel à ces fonctions. Nous allons voir aujourd'hui les étudier un peu plus en détails.

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

Evaluation: 7 questions

  07-11-12-13-14-19

  20

Exercices supplémentaires 🏠 : oui

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

Documents de cours à recopier PDF : .PDF

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 :

  • reçoivent des données d'entrée et
  • renvoient leur réponse en sortie.

Un exemple avec la fonction VALEUR_ABSOLUE qui n'attend qu'une entrée (un nombre) et qui renvoie la valeur absolue du nombre (le même nombre mais sans son signe).

-12  ⇒   Fonction VALEUR ABSOLUE   ⇒  12

12  ⇒   Fonction VALEUR ABSOLUE   ⇒  12
D'autres exemples
5; 10; 50; 0; 20  ⇒   Fonction MAXIMUM   ⇒  50

5; 10; 50; 0; 20  ⇒   Fonction MINIMUM   ⇒  0

5; 10; 50; 0; 20  ⇒   Fonction SOMME   ⇒  85
(Rappel) 1.2 - FONCTION : native

Une fonction native est une fonction présente de base dans Python.

On en fait l'appel en notant son nom et en fournissant entre parenthèses les entrées séparées par des virgules.

nom_fonction(a, b, c, d...)

Dans le cadre de ce site, les fonctions natives sont toujours écrites en vert.

Fonction VALEUR ABSOLUE : cette fonction se nomme abs() en Python.

>>> abs(-12) 12 >>> abs(12) 12

Fonction MAXIMUM : cette fonction se nomme max() en Python.

>>> 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 développeurs qui vont lire le programme en utilisant le caractère #. Voir la partie suivante pour un exemple.

Attention : sur votre ligne, tout ce qui se trouve derrière ce # sera purement ignoré par l'interpréteur.

B - Lancer un appel de fonction

Lancer un appel revient à fournir les entrées et attendre la réponse de la fonction.

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 étant 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écuté ces lignes, l'interpréteur Python ne lance pas d'appel à votre fonction. D'ailleurs, il aurait du mal... Que mettrait-il dans x de toutes manières ?

Déclarer la fonction permet juste de mettre ce nom en mémoire pour pouvoir y faire appel plus loin dans le programme.

1.6 return : on peut utiliser directement une expression

Lorsque l'interpréteur rencontre le mot-clé return, il réalise deux choses :

  1. Il évalue l'expression située derrière return.
  2. Il renvoie la valeur trouvée à l'endroit d'où a été lancé l'appel initialement.

Deux choix de conception :

  • Stocker d'abord le résultat dans une variable, puis placer ce nom de variable derrière return.
  • 1 2 3
    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.

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

  • La déclaration qui indique les variables locales permettant de stocker les entrées qu'on fournira un jour.
    Plutôt que de dire les variables locales de stockage d'entrées qu'on fournira un jour", on dira les paramètres.
  • 1 2 3
    def valeur(d, u): # d et u sont les paramètres déclarés resultat = d*10 + u return resultat

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

  • L'appel qui fournit les entrées envoyées à la fonction sur cet appel. Plutôt que de dire les "entrées envoyées à la fonction sur cet appel", on utilisera le mot argument.
  • >>> valeur(5, 3) # 5 et 3 sont les Arguments d'Appel 53

    L'argument n°1 (5) va dans le paramètre n°1 (d), ect...

Cas d'une fonction avec 1 paramètre
1
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.

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 at 0x de l'onglet "Variables" ... fait référence à une zone mémoire de stockage et cela devrait donc être différent sur votre machine. Par contre, vous devriez avoir quelque chose de proche à l'écran :

    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 cette variable est de type function : elle référence des instructions.
  7. >>> fois2 <function fois2 at 0x7f2d96628af0> >>> type(fois2) <class 'function'>

02° On dispose de ces deux fonctions en mémoire.

1 2 3 4 5 6 7
def fois2(x): resultat = x * 2 return resultat def plus2(x): resultat = x + 2 return resultat

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 le contenu du paramètre x lors de cet appel ?
  3. Quelle est la réponse de cet appel (qui s'affiche dans la console interactive) ?
>>> 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. Quelle est la réponse de cet appel ?
>>> 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. Quelle est la réponse de cet appel ?

...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. Cet appel calcule 10+2 et renvoie 12.
>>> plus2(15) 17
  1. Cette fois, on envoie 15.
  2. x référence 15.
  3. Cet appel calcule 15+2 et renvoie 17.

>>> fois2(10) 20
  1. On envoie une entrée valant 10.
  2. x référence 10.
  3. Cet appel calcule 2*10 et renvoie donc 20.

03° Un peu plus complexe que la précédente. Réaliser les appels suivants dans la console de Python, observer les réponses et puis répondre aux questions :

>>> a = plus2(100)
  1. Quel est l'argument de cet appel ?
  2. Quel est le contenu de x lors de cet appel ?
  3. Quelle est la réponse de cet appel ?
  4. Pourquoi n'obtient-on pas d'affichage dans la console ?
>>> b = fois2(a)
  1. Quel est l'argument de cet appel ?
  2. Quel est le contenu du paramètre x lors de cet appel ?
  3. Quelle est la réponse de cet appel ?
  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() ?
  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 compte 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° Répondre à trois ensembles de questions ci-dessous.

A - AVANT d'avoir exécuter quoi que ce soit

Fournir la succession des lignes suivies par l'interpréteur Python lors de l'exécution de ce programme.

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)

B - Python Tutor

Aller sur Python Tutor, et visualiser le déroulé du programme suivant en mode pas à pas.

C - APRES Python Tutor

  1. Lorsqu'on exécute la ligne 1, met-on la fonction en mémoire ou lance-t-on la fonction ?
  2. Les variables d et u existent-elles dans l'espace des noms global ?
  3. Que devient son espace des noms local après que la fonction ai répondu ?
  4. Les variables a, b et c sont-elles des variables locales ou globales ?

...CORRECTION...

Partie A

L1 (déclaration)

L5 (appel en envoyant 2 et 3) - L1 - L2 - L3(réponse)

L5 (réception puis affectation)

L6 (appel en envoyant 2 et 3) - L1 - L2 - L3(réponse)

L6 (réception puis affectation)

L7 (appel en envoyant 2 et 3) - L1 - L2 - L3(réponse)

L7 (réception puis affectation)

Fin

Partie C

  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. d et u sont des variables locales.

  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 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) ???

...CORRECTION...

Le 3 est stocké dans la variable locale x. La première fonction calcule donc 3*10+5, et renvoie 35.

En comparant appel et déclaration pour la deuxième fonction, on voit que x reçoit 5, y reçoit 6 et z reçoit 3. La fonction calcule 500+60+3 et renvoie 563.

08° Pour valider cette première partie :

  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 possibilité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 phrase 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 à mot.
  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() renvoie les informations fournies dans la documentation.

>>> help(addition) Help on function addition in module __main__: addition(nbr1: int, nbr2: int) -> int Renvoie la somme de nbr1 et nbr2

10-A° Modifier alors calculer_moyenne() pour qu'elle renvoie la réponse attendue : il faudra lire la documentation pour le savoir.

1 2 3 4 5
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'utilisateur 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 celle de la question précédente mais que la documentation est juste plus fournie lorsqu'on utilise help().

>>> moy 15.0 >>> moy2 = calculer_moyenne(10, 15) >>> moy2 12.5 >>> help(calculer_moyenne) Help on function calculer_moyenne in module __main__: calculer_moyenne(note1, note2) Renvoie la moyenne des deux notes :: param note1(int) :: une note dans [0;20] :: param note2(int) :: une note dans [0;20] :: return (float) :: la moyenne de note1 et note2

...CORRECTION...

  • Sur quelles lignes trouve-t-on les types attendus des deux paramètres ?
  • 4 5
    :: param note1(int) :: une note dans [0;20] :: param note2(int) :: une note dans [0;20]
  • Quelles sont les préconditions sur les paramètres qu'on trouve dans cette documentation plus longue ?
  • 4 5
    :: param note1(int) :: une note dans [0;20] :: param note2(int) :: une note dans [0;20]
  • Sur quelle ligne se trouve l'indication sur le type de la réponse dans cette documentation ?
  • 6
    :: return (float) :: la moyenne de note1 et note2

11° Compléter les fonctions addition(), multiplication() et division_euclidienne() pour qu'elles fonctionnent correctement. Enregistrer et lancer dans Thonny pour placer cette fonction en mémoire. Faire les appels dans la console pour vérifier le fonctionnement.

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 x par y :: 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)

...CORRECTION...

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 note1 + note2 def multiplication(x, y): """Renvoie la multiplication de x par y :: param x(int) :: un entier :: param y(int) :: un entier :: return (int) :: x*y """ return x * y 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 (a // b, a % b)

12° Placer la fonction en mémoire. Réaliser les appels proposés pour comprendre ce que fait cette fonction.

Question A : écrire la documentation complète (plusieurs lignes) de cette fonction une fois que vous avez compris ce qu'elle fait.

Question B : écrire la documentation en utilisation une documentation rapide (prototype et une seule ligne).

1 2 3 4 5 6 7 8 9 10
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.

...CORRECTION...

1 2 3 4 5 6 7 8 9 10
import random def de(nb_faces): '''Renvoie un nombre aléatoire entre 1 et nb_faces inclus. :: nb_faces(int) : valeur maximale voulue pour le dé :: :: return (int) : un entier aléatoire dans l'intervalle [1; nb_faces] :: ''' return random.randint(1, nb_faces)
1 2 3 4 5
import random def de(nb_faces:int) -> int: '''Renvoie un nombre aléatoire entre 1 et nb_faces inclus.''' return random.randint(1, nb_faces)

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.

...CORRECTION...

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""" return len(chaine) >= 6

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

1 2 3 4 5 6 7 8 9 10 11
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

...CORRECTION...

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 """ return chaine1 in chaine2

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éatoire 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 toujours 1. Et pourtant, il faut toujours lui fournir cette valeur puisqu'il y a deux arguments à envoyer.

Si on ne met qu'un seul paramètre attendu et qu'on impose que la valeur de départ est toujours 1, on obtient une fonction moins flexible.

Comment avoir le meilleur des deux mondes ?

4.1.2 Paramètres par défaut

On peut donner des valeurs d'arguments par défaut aux fonctions. Si l'utilisateur ne fournit pas de valeurs pour ces paramètres, il sera rempli automatiquement avec la valeur située un signe = dans la déclaration.

1 2 3 4 5 6 7 8 9 10 11
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 d'un programme pyxel

Logo Pyxel
Logo Pyxel

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

Déroulé fondamental

On peut résumer le déroulé d'un programme Pyxel de cette façon 

  • Séquence de réglage : init() puis run()
  • 30 fois par seconde : controle() puis vue()
Déroulé plus détaillé

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

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

    3 - TANT QUE le programme est actif :

      On mémorise les actions de l'utilisateur

      SI la dernière activation date de plus de 1/30e de seconde

        On active la fonction controle() qui va

        faire appel à différentes fonctions pour mettre à jour les données

        On active la fonction 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

Système d'axes

Le point (0,0) est le bord haut gauche.

L'abscisse va bien vers la gauche.

Par contre, l'ordonnée est orientée vers le bas, attention.

Les axes dans Pyxel

Nous allons revenir sur l'application Pyxel de l'activité précédente et en fournir une correction.

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 10-11 : le dictionnaire vaisseau est-il une variable globale ou locale ?
  2. Lignes 43 : le dictionnaire vaisseau est-il déclaré comme un paramètre ?
  3. Lignes 46-48-50 : l'affectation est-elle faite sur le dictionnaire vaisseau ou sur la valeur associée à l'une des clés ? En conséquence, vaisseau 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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
# 1 - Importations ============================================================ import pyxel import random # 2 - CONSTANTES et variables globales ======================================== vaisseau = {'x': 100, # la clé 'x' correspond à l'abscisse du vaisseau 'y': 100} # la clé 'y' correspond à l'ordonnée du vaisseau points = {'score':0} # +1 par ennemi détruit, -5 par ennemi qui passe tirs = {} # dict des tirs contenant des tableaux [x, y] explosions = {} # dict des explosions contenant des tableaux [x, y, rayon] ennemis = {} # dict des ennemis contenant des tableaux [x, y, couleur] # 3 - FONCTIONS =============================================================== def controle(): """[C] Fonction qui récupère l'action, modifie les données en conséquence""" if pyxel.btn(pyxel.KEY_LEFT): # Si on maintient appuyé la flèche GAUCHE deplacer_vaisseau(-1, 0) # --> Mettre à jour les coordonnées du vaisseau if pyxel.btn(pyxel.KEY_RIGHT): # Si on maintient appuyé la flèche DROITE deplacer_vaisseau(1, 0) # --> Mettre à jour les coordonnées du vaisseau if pyxel.btnp(pyxel.KEY_SPACE): # Si on a appuyé sur la touche ESPACE x = vaisseau['x'] # --> récupère x y = vaisseau['y'] # --> récupère y creer_tir(x, y) # --> Rajoute un tir dans le dictionnaire tirs if pyxel.frame_count % 30 == 0: # S'il s'est écoulé 30 secondes creer_ennemi() # insère un nouvel ennemi gerer_explosions() # Mettre les explosions ç jour en les amplifiant/supprimant gerer_tirs() # Mettre les tirs à jour (déplacement et collision) gerer_ennemis() # Mettre les ennemis à jour def deplacer_vaisseau(dx, dy): """[M] Modifie les données du vaisseau pour intégrer le déplacement dx et dy voulu""" vaisseau['x'] = vaisseau['x'] + dx # modifie l'abscisse du vaisseau if vaisseau['x'] > 120: # Si on va trop loin à droite vaisseau['x'] = 120 # --> bloque l'abscisse à 120 if vaisseau['x'] < 0: # Si on va trop loin à gauche vaisseau['x'] = 0 # --> bloque l'abscisse à 0 def creer_tir(x, y): """[M] Rajoute ce tir tiré depuis un vaisseau en (x, y) dans le tableau des tirs""" tir = [x + 4, y - 4] # crée le tableau contenant les coordonnées du tir tirs[pyxel.frame_count] = tir # stocke le tir dans tirs avec cle = pyxel.frame_count def gerer_explosions(): """[M] Modifie ou supprime les données liées aux explosions""" for cle in tuple(explosions.keys()): # Pour chaque clé du dictionnaire explosions explosion = explosions[cle] # récupère l'une des explosions (un tableau) explosion[2] = explosion[2] + 2 # augmente le rayon de 2 pixels if explosion[2] > 8: # Si le rayon est supérieur à 8 del explosions[cle] # --> supprime cette explosion du dictionnaire def gerer_tirs(): """[M] Modifie les coordonnées des tirs, en les supprimant au besoin""" for cle in tuple(tirs.keys()): # Pour chaque cle du dictionnaire tirs tir = tirs[cle] # récupère l'adresse d'un tableau contenant un tir tir[1] = tir[1] - 1 # fait "monter" le tir x = tir[0] # --> récupère son abscisse y = tir[1] # --> récupère son ordonnée if tir[1] < 0: # Si le tir percute le bord du haut explosions[cle] = [x, y, 1] # --> génère une explosion dans le dico explosions del tirs[cle] # --> supprime ce tir du dico tirs else: # Sinon for cle2 in tuple(ennemis.keys()): # --> pour chaque cle du dictionnaire ennemis ennemi = ennemis[cle2] # ----> récupère cet ennemi if tir_touche(x, y, ennemi): # si cet ennemi est touché par ce tir explosions[cle] = [x, y, 1] # --> ajoute l'explosion dans le dico explosions del ennemis[cle2] # --> supprime cet ennemi du dico ennemis del tirs[cle] # --> supprime ce tir du dico tirs points["score"] = points["score"] + 1 # Augmente le score car ennemi abattu def tir_touche(x, y, ennemi): """Prédicat qui renvoie True si le tir (x, y) touche l'ennemi""" xg = ennemi[0] # récupère son abscisse à gauche xd = ennemi[0] + 8 # récupère son abscisse à droite yh = ennemi[1] # récupère son abscisse en haut yb = ennemi[1] + 8 # récupère son abscisse en bas if x >= xg: # si l'abscisse du tir dépasse le côté gauche de l'ennemi if x <= xd: # si l'abscisse du tir précède le côté droite de l'ennemi if y <= yb: # si le tir est (à l'écran) au dessus du côté bas (Oy vers le bas) if y >= yh: # si le tir est (à l'écran) en dessous du côté haut return True # --> si on arrive ici, c'est que cet ennemi est touché return False # --> si on arrive ici, c'est qu'on a pas rencontré le True ! def creer_ennemi(): """[M] Création et ajout d'un ennemi dans le dictionnaire ennemis""" x = random.randint(0, 120) # génère une abscisse aléatoire entre 0 et 120 couleur = random.randint(2, 15) # génère une couleur aléatoire entre 2 et 15 ennemi = [x, 0, couleur] # génère le tableau [x, y, couleur] ennemis[pyxel.frame_count] = ennemi # rajoute cet ennemi dans le dictionnaire def gerer_ennemis(): """[M] Modifie les coordonnées des ennemis en les suprimmant au besoin""" for cle in tuple(ennemis.keys()): # Pour chaque cle du dictionnaire ennemis ennemi = ennemis[cle] # récupère l'adresse d'un tableau contenant un ennemi ennemi[1] = ennemi[1] + 1 # fait "descendre" l'ennemi if ennemi[1] > 120: # Si l'ennemi percute le bord du bas del ennemis[cle] # --> supprime cet ennemi du dictionnaire points["score"] = points["score"] - 5 # -5 car l'ennemi a parvenu à passer def vue(): """[V] création de l'affichage (30 fois par seconde)""" pyxel.cls(0) # vide la fenetre afficher_vaisseau() # vaisseau (carre 8x8) afficher_tirs() # tous les tirs (rectangle 1x4) afficher_ennemis() # tous les ennemis (carré) afficher_explosions() # toutes les explosions (cercle) afficher_informations() # les textes def afficher_vaisseau(): """[V] Dessine le vaisseau""" x = vaisseau['x'] # récupère l'abscisse y = vaisseau['y'] # récupère l'ordonnée pyxel.rect(x, y, 8, 8, 1) # trace un carré 8*8 de couleur 1 def afficher_tirs(): """[V] Dessine les tirs""" for cle in tirs.keys(): # pour chaque indice possible dans le tableau tirs tir = tirs[cle] # récupère dans tir le tir n°i x = tir[0] # récupère l'abscisse y = tir[1] # récupère l'ordonnée pyxel.rect(x, y, 1, 3, 10) # trace un rectangle 1*3 de couleur 10 def afficher_explosions(): """[V] Dessine les explosions""" for cle in explosions.keys(): # pour chaque indice posible dans le tableau explosions explosion = explosions[cle] # récupère l'une des explosions (un tableau) x = explosion[0] # récupère son abscisse y = explosion[1] # récupère son ordonnée r = explosion[2] # récupère son rayon pyxel.circ(x, y, r, 10) # trace un cercle de couleur 10 pyxel.circ(x, y, r-3, 0) # trace un cercle plus petit de couleur 0 def afficher_ennemis(): """[V] Dessine les ennemis""" for cle in ennemis.keys(): # pour chaque cle du dictionnaire ennemis ennemi = ennemis[cle] # récupère l'un des ennemis (un tableau) x = ennemi[0] # récupère sa abscisse y = ennemi[1] # récupère son ordonnée c = ennemi[2] # récupère sa couleur pyxel.rect(x, y, 8, 8, c) # trace un carré 8*8 def afficher_informations(): """[V] Affiche les informations voulues à l'écran""" texte = str(points["score"]) # génère un string à partir du nombre de frames pyxel.text(1, 1, texte, 7) # affiche le texte def lancer_jeu(): """[C] Cette fonction lance la surveillance des événements""" pyxel.init(128, 128, title="Mon premier jeu") # Initialisation de la fenêtre pyxel.run(controle, vue) # Lancement de l'alternance controle / vue # 4 - PROGRAMME PRINCIPAL ==================================================== lancer_jeu()

...Correction...

  1. Lignes 10-11 : vaisseau est une variable globale puisque déclarée dans le programme lui-même.
  2. Ligne 43 : le dictionnaire vaisseau ne fait pas partie des paramètres transmis.
  3. Lignes 46-48-50 : l'affectation est faite sur le CONTENU du dictionnaire, on ne modifie pas le conteneur lui-même. En conséquence, vaisseau 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 vaisseau est une variable globale ou locale dans la fonction afficher_vaisseau() ?

...Correction...

    Le nom vaisseau n'apparaît pas en tant que paramètre et il n'y a aucune affectation directe sur vaisseau. 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 d'un 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_...() 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_...() correspondante qui va

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

        Ensuite demander à modifier l'affichage (la vue) en faisant appel à 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

On peut résumer cela en 

  • Séquence de réglage : bind() puis mainloop()
  • Dès qu'un événement survient : controle_...() puis vue()

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 vaisseau est-il une variable globale ou locale ?
  2. Lignes 34 : le dictionnaire vaisseau est-il déclaré comme un paramètre ?
  3. Lignes 37-41 : l'affectation est-elle faite sur le conteneur-dictionnaire vaisseau ou sur le contenu du dictionnaire associé à l'une des clés ? En conséquence, vaisseau 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 ============================================ vaisseau = {'x': 100, 'y': 50} # Dictionnaire stockant les données modifiables # 3 - FONCTIONS ================================================================== def controle(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 deplacer_vaisseau(-3, 0) # Modifie en mémoire la position du vaisseau if touche == "Right": deplacer_vaisseau(3, 0) # Modifie en mémoire la position du vaisseau vue() def deplacer_vaisseau(dx, dy): # MODELE """Modifie les données du vaisseau pour intégrer le déplacement dx et dy voulu""" vaisseau['x'] = vaisseau['x'] + dx if vaisseau['x'] > 500: vaisseau['x'] = 500 if vaisseau['x'] < 0: vaisseau["x"] = 0 def vue(): # VUE """Efface puis dessine la nouvelle vue""" plateau.delete("all") # Efface les dessins dans le canvas afficher_vaisseau(vaisseau['x'], vaisseau['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) vue() # Dessine le vaisseau à la position initiale fenetre.bind("<Any-KeyPress>", controle) # Lie le clavier à la fonction fournie fenetre.mainloop() # Lance la surveillance des événements

...Correction...

  1. Lignes 17 : vaisseau est une variable globale puisque déclarée dans le programme lui-même.
  2. Lignes 34 : le dictionnaire vaisseau ne fait pas partie des paramètres transmis.
  3. Lignes 37-39-41 : l'affectation est faite sur le CONTENU du dictionnaire. On ne modifie pas le conteneur en lui-même. En conséquence, vaisseau 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 vaisseau est une variable globale ou locale dans la fonction vue() ?

...Correction...

    Le nom vaisseau n'apparaît pas en tant que paramètre et il n'y a aucune affectation directe sur vaisseau. 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 controle() 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 deplacer_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.