Infoforall

Python 12 : LES FONCTIONS - Portée des variables

Nous allons voir un aspect plus technique des fonctions : la portée des variables. Derrière ce terme, se cache la possibilité ou non pour deux parties d'un programme de voir ou de manipuler les variables des autres parties.

Imaginez

La question qu'on va se poser est alors :

"Une variable située dans une fonction peut-elle être vue depuis le programme principal ?"

"Une (élève) située dans une (salle) peut-elle être vue depuis la (cour du lycée) ?"

Attention : ce que nous allons voir ici est valable pour Python 3. Ce n'est pas forçément le cas pour les autres langages. Les choix effectués sur la portée des variables est un choix propre à chaque langage.

Or, parfois, on veut qu'une fonction puisse modifier une variable du programme. Prenons le cas de notre Simon : on veut que le programme mémorise les cases activées par l'utilisateur. Les cases sont activées via des fonctions. Il faut donc bien mémoriser la séquence hors de la fonction, sinon l'information disparait une fois qu'on sort de la fonction ...

Voici ce qu'on veut obtenir :

Enfin, cette activité est fondamentale au niveau de la création d'un projet d'envergure. Pourquoi ? Nous verrons comment stocker des données en nombre inconnu et agir sur celles-ci. Comment ? En agissant sur des listes depuis des fonctions.

1 - Portée des variables

Là, nous allons rentrer dans un problème bien spécifique à la programmation : la portée du nom d'une variable, ou autrement posé, le problème de la reconnaissance d'une variable par l'ordinateur.

1-1 Espace des noms

Pour éclaircir cette notion, nous allons utiliser le programme suivant :

01° Lire le code et tenter de trouver ce que doit afficher l'ordinateur. Lancer ensuite le code pour le vérifier.

#!/usr/bin/env python

# -*- coding: utf-8 -*-


# - - - - - - - - - - - - - - - - - -

# Déclarations des fonctions

# - - - - - - - - - - - - - - - - - -


def fois_trois(x):

    x = x*3

    print("Dans la fonction fois_trois, \tx vaut \t",x, "\t et son id vaut \t", id(x))

    return(x)


# - - - - - - - - - - - - - - - - - -

# Corps du programme

# - - - - - - - - - - - - - - - - - -


x = 2

resultat = fois_trois(6)

print("Dans le corps du programme, \tx vaut \t",x, "\t et son id vaut \t", id(x))

input("Appuyer sur ENTREE")

Cela devrait donner :

Dans la fonction trois_fois,    x vaut    18   et son id vaut    1400354400

Dans le corps du programme,     x vaut    2    et son id vaut    1400353888

Appuyer sur ENTREE

Mais quelle est cette magie ? x devrait valoir 18, l'ordinateur fait n'importe quoi !

En réalité, l'ordinateur fait ce qu'on lui dit. D'où vient le problème ? De nous bien entendu.

Nous voyons x dans le corps du programme et x dans la fonction et on en déduit qu'il s'agit de la même entité. Ca nous parait "normal". Attention aux intuitions en informatique : ne pouvez-vous pas connaitre deux personnes qui portent le même nom sans être la même personne ? Et oui, c'est possible.

Il peut y avoir un Guillaume Owsinski à Lille et un Guillaume Owsinski à Paris. Si on parle de Guillaume Owsinski en étant à Paris, on fait référence à celui qui habite à Paris, pas à l'autre. Aucune confusion possible. Et bien, c'est pareil avec les variables.

ESPACE DES NOMS : Lorsque vous voyez une variable x, rajoutez mentalement : "x du programme principal" ou "x de la fonction fois_trois"... Vous allez voir que vous ne les confondrez plus avec ce petit truc. Dans quelques temps, cela vous paraitra naturel de faire la distinction et vous n'aurez plus à faire ceci.

variables locales

Voici le même programme en version animée et en rajoutant une couleur aux x (Jaune-Orange pour l'espace des noms du corps du programme ou Bleu pour espace des noms de la fonction).

#!/usr/bin/env python

# -*- coding: utf-8 -*-


# - - - - - - - - - - - - - - - - - -

# Déclarations des fonctions

# - - - - - - - - - - - - - - - - - -


def fois_trois(x) :

    x = x*3

    print("Dans la fonction fois_trois, \tx vaut \t",x, "\t et son id vaut \t", id(x))

    return(x)


# - - - - - - - - - - - - - - - - - -

# Corps du programme

# - - - - - - - - - - - - - - - - - -


x = 2

resultat = fois_trois(6)

print("Dans le corps du programme, \tx vaut \t",x, "\t et son id vaut \t", id(x))

input("Appuyer sur ENTREE")

CLIQUEZ ICI POUR VOIR LE CONTENU DES VARIABLES :

x :

resultat :

x :

On voit ainsi que :

1-2 Portée des variables déclarées dans une fonction

Et si on tente d'afficher x dans le programme principal alors qu'on y fait référence uniquement dans la fonction ?

02° Vérifier bien que l'affectation de x n'apparait ci-dessous que dans la fonction mais qu'on tente de l'afficher dans le programme principal. Tester le code ensuite.

#!/usr/bin/env python

# -*- coding: utf-8 -*-


# - - - - - - - - - - - - - - - - - -

# Déclarations des fonctions

# - - - - - - - - - - - - - - - - - -

def fois_trois(x) :

    x = x*3

    print("Dans la fonction fois_trois, x vaut \t",x)

    return(x)


# - - - - - - - - - - - - - - - - - -

# Corps du programme

# - - - - - - - - - - - - - - - - - -

resultat = fois_trois(6)

print("Dans le corps du programme, x vaut \t",x)

input("Appuyer sur ENTREE")

On voit ainsi que :

Vous devriez avoir une erreur :

    Dans la fonction fois_trois, x vaut 18

Traceback (most recent call last):

File "G:\informatique\python\cours_10_fonctions\lec10_q2.py", line 16, in <module>

print("Dans le corps du programme, x vaut \t",x)

NameError: name 'x' is not defined

Pourquoi ?

Vous tentez dans le corps du programme d'afficher une variable  x  (du programme principal) qui n'existe pas dans l'espace des noms du corps du programme : aucune affectation avec un x n'est apparu dans l'exécution séquentielle du programme principal. La variable  x  de la fonction n'existe que lors de l'exécution de la fonction. D'ailleurs, pour limiter les données stockées inutilement en mémoire, la case ayant servi à stocker  x  est immédiatement liberée une fois qu'on sort de la fonction. C'est le "ramasse-miettes" qui s'occupe de cette destruction. On dit garbage collector en anglais.

variables locales

ESPACE DES NOMS : une variable locale d'une fonction n'a d'existence qu'à l'intérieur de la fonction lors de son exécution. L'espace des noms d'une fonction est reservé à cette fonction. Aucune autre partie du programme n'a accès à cet espace des noms.

portée depuis les fonctions

1-3 Portée des variables déclarées dans le programme principal

Les variables déclarées dans le corps du programme sont-elles accessibles depuis les fonctions ? Vous allez tenter de répondre à cela à l'aide de ce petit code :

#!/usr/bin/env python

# -*- coding: utf-8 -*-


# - - - - - - - - - - - - - - - - - -

# Déclarations des fonctions

# - - - - - - - - - - - - - - - - - -


def une_fonction(x) :

    print("Dans la fonction, z vaut \t",z)

    return(x)


# - - - - - - - - - - - - - - - - - -

# Corps du programme

# - - - - - - - - - - - - - - - - - -


z = 300

resultat = une_fonction(6)

print("Dans le corps prin., z vaut \t",z)

input("Appuyer sur ENTREE")

03° Utiliser le code précédent pour répondre à la question sur la visibilité du z du programme "principal" depuis une fonction : depuis l'intérieur de la fonction une_fonction peut-on voir la variable z du corps du programme ?

04° Rajouter z = z*2 comme première instruction de la fonction. Relancer. Peut-on modifier la valeur de z définie dans le corps de la fonction depuis l'intérieur de la fonction ?

A RETENIR : une variable locale du corps du programme peut être lue à l'intérieur d'une fonction à laquelle on fait appel. Par contre, on ne pourra pas en modifier la valeur.

variables locales

Si on résume :

  • Chaque partie du programme possède son propre espace des noms : partie principale et les fonctions.
  • Une variable du programme principal peut être lue mais pas modifiée dans les fonctions.
  • Une variable interne à une fonction n'a pas d'existence en dehors de cette fonction.

1-4 Arguments et paramètres

Il reste à voir le cas des arguments et des paramètres. Lorsqu'on envoie un argument à une fonction, peut-elle en changer la valeur ? Et bien, la réponse est NON. Lorsque vous fournissez une variable à une fonction, vous lui fournissez une copie du contenu, pas son adresse réelle.

Si vous voulez vous en convaincre, voilà un programme qui fournit en argument une variable z à la fonction nommée une_fonction. Dans une_fonction, on modifie la valeur du paramètre. Une fois revenu dans le corps du programme, la variable z est restée inchangée :

#!/usr/bin/env python

# -*- coding: utf-8 -*-


# - - - - - - - - - - - - - - - - - -

# Déclarations des fonctions

# - - - - - - - - - - - - - - - - - -


def une_fonction(x) :

    x = x*2

    print("Dans la fonction, x vaut \t",x)

    return(x)


# - - - - - - - - - - - - - - - - - -

# Corps du programme

# - - - - - - - - - - - - - - - - - -


z = 300

print("Dans le corps prin., z vaut \t",z," avant l'appel de la fonction")


resultat = une_fonction(z)


print("Dans le corps prin., z vaut \t",z," après l'appel de la fonction")

input("Appuyer sur ENTREE")


05° Testez pour vérifier.

Dans le corps princ., z vaut 300 avant l'appel de la fonction

Dans la fonction, x vaut 600

Dans le corps princ., z vaut 300 après l'appel de la fonction

A RETENIR : une variable transmise en argument ne sera pas modifiée même si la fonction modifie le contenu du paramètre correspondant. On dit que la variable est transmise en valeur car le programme n'envoie que la valeur de la variable ( et pas l'adresse ou l'identidfiant de la "case" dans laquelle elle est située).

La fonction n'a ainsi pas les moyens physiques d'atteindre cette case et d'y modifier quoi que ce soit.

argument et parametre

1-5 Deux variables portant le même nom. Laquelle choisir ?

06° Un exercice pour faire si vous avez compris. Que se passe-t-il s'il existe une variable x dans le corps du programme et une autre variable x dans une fonction ? Réaliser un code utilisant des variables portant le même nom et ayant des valeurs différentes (par exemple x = 5 dans le corps du programme et x = 200 dans la fonction). Demander d'affichage de x dans le corps du programme et dans la fonction. Conclusion ?

C'est donc simple : dans une fonction, on commence par chercher une variable qui porte le nom demandé dans l'espace des noms de la fonction. Si on ne trouve pas, on tente de trouver une variable qui porte le bon nom dans le corps du programme. Mais dans ce cas, on ne pourra que lire le contenu, pas en modifier la valeur.

Resumé :

On peut donner le même nom à des variables situées dans des structures différentes :

  • x déclarée dans le corps du programme,
  • x déclarée dans fonction_1 et
  • x déclarée dans fonction_2

désignent trois entités différentes.

variables locales

Une variable d'une fonction n'est pas visible en dehors de cette fonction..

portée depuis les fonctions

Cela revient à dire qu'on ne peut pas voir un élève depuis la cour ou depuis une autre salle.

Une variable du corps du programme est visible des fonctions rattachées. Par contre, elle ne sera pas modifiable depuis les fonctions.

variables locales

Cela revient à dire qu'un élève dans la cour est visible depuis les salles (il y a des fenêtres) mais qu'on ne peut pas le modifier, communiquer avec lui.

Une fonction commence toujours par chercher une variable locale dans son propre espace des noms. Si aucune variable locale ne porte le bon nom, l'interpréteur va voir dans l'espace des noms du corps du programme.

1-6 Communiquer avec le programme principal

Bon, les fonctions ne peuvent pas modifier les variables données dans le programme lui-même. C'est assez limitant. Comment faire pour qu'une fonction puisse néanmoins modifier les variables du corps du programme ?

La façon la plus propre de faire cela est d'affecter la variable qu'on veut modifier à l'aide du return de la fonction elle-même.

07° Tester la fonction mise_au_carre(valeur) qui renvoie avec le return le carré du paramètre valeur. On modifie la valeur de x du programme principal en l'affectant avec ce que renvoie la fonction. Vérifier que cela fonctionne.

#!/usr/bin/env python

# -*- coding: utf-8 -*-


# - - - - - - - - - - - - - - - - - -

# Déclarations des fonctions

# - - - - - - - - - - - - - - - - - -


def mise_au_carre(valeur) :

    return(valeur**2)


# - - - - - - - - - - - - - - - - - -

# Corps du programme

# - - - - - - - - - - - - - - - - - -


x = 3


for repetition in range(4) :

    print("x a comme valeur ",x," avant l'appel de la fonction")

    x = mise_au_carre(x)

    print("x a comme valeur ",x," après l'appel de la fonction\n")


input("Appuyer sur ENTREE")

Vous devriez obtenir :

x a comme valeur 3 avant l'appel de la fonction

x a comme valeur 9 après l'appel de la fonction


x a comme valeur 9 avant l'appel de la fonction

x a comme valeur 81 après l'appel de la fonction


x a comme valeur 81 avant l'appel de la fonction

x a comme valeur 6561 après l'appel de la fonction


x a comme valeur 6561 avant l'appel de la fonction

x a comme valeur 43046721 après l'appel de la fonction


Appuyer sur ENTREE

Voilà, vous savez comment modifier proprement la valeur d'une variable du programme principal à l'aide d'une fonction. Pourquoi proprement ? Car le programme principal n'a absolument pas besoin de savoir comment la fonction calcule le carré à l'interne. Imaginons qu'on change le nom x du paramètre de la fonction par une variable caChange :

#!/usr/bin/env python

# -*- coding: utf-8 -*-


# - - - - - - - - - - - - - - - - - -

# Déclarations des fonctions

# - - - - - - - - - - - - - - - - - -


def mise_au_carre(caChange) :

    return(caChange**2)


# - - - - - - - - - - - - - - - - - -

# Corps du programme

# - - - - - - - - - - - - - - - - - -


x = 3


for repetition in range(4) :

    print("x a comme valeur ",x," avant l'appel de la fonction")

    x = mise_au_carre(x)

    print("x a comme valeur ",x," après l'appel de la fonction\n")


input("Appuyer sur ENTREE")

08° Vérifier que cela fonctionne toujours.

C'est ce qu'on nomme l'encapsulation : les deux codes communiquent mais ne sont pas interdépendants des noms de variables de l'autre : on peut modifier le code du corps principal sans changer celui de la fonction, ou modifier celui de la fonction sans avoir à modifier celui du corps principal.

Et ça, lorsqu'on travaille en groupe, c'est carrément bien ! Si votre collègue décide de modifier son code, cela n'affecte pas votre travail !

Nous allons voir maintenant comment faire le Mal. A savoir comment faire pour que la fonction modifie réellement la variable du corps du programme depuis l'intérieur même de la fonction. Sans return sans rien. Pouf. Et nous allons voir pourquoi ce n'est pas une si bonne idée que cela. On utilise parfois cette méthode pour aller vite, mais gardez bien à l'esprit qu'il faudra l'utiliser avec parcimonie.

2 - Variables globales

La compréhension totale des parties suivantes est plutôt optionnelle : c'est en programmant que vous comprendrez réellement. Sur des cas concrets, vous arriverez certainement mieux à en voir l'intêret. Par contre, lisez au moins tout ceci jusqu'au bout. Ca en vaut la peine.

Reprécisons la situation : une fonction peut lire une variable déclarée dans le corps du programme mais ne peut pas la modifier. C'est un avantage conséquent car cela rend le code plus solide.

Néanmoins, on peut vouloir modifier la valeur d'une variable du corps du programme depuis une fonction.

Dans ce cas, il faut rajouter une ligne de code dans la fonction elle-même : si on veut modifier une variable ma_variable située dans le corps du programme, il suffit de rajouter global ma_variable en début de la fonction.

Exemple ci-dessous. Vous noterez que je déclare bien à part ma variable x de faire à bien faire comprendre qu'elle sera destinée à être globale et donc à être reconnue par des fonctions : si quelqu'un veut modifier le code, il sait qu'il ne doit pas changer le nom de cette variable. Rien ne vous oblige à l'initialiser ainsi à part, mais ça évite de faire des bétises lors d'une modification quelques semaines plus tard.

#!/usr/bin/env python

# -*- coding: utf-8 -*-


# - - - - - - - - - - - - - - - - - -

# Déclarations des variables globlales

# - - - - - - - - - - - - - - - - - -


x = 3 # x est destinée à être globale


# - - - - - - - - - - - - - - - - - -

# Déclarations des fonctions

# - - - - - - - - - - - - - - - - - -


def mise_au_carre() :

    global x # x local obtient le même id que x principal

    x = x**2


# - - - - - - - - - - - - - - - - - -

# Corps du programme #

# - - - - - - - - - - - - - - - - - -


for repetition in range(4) :

    print("x a comme valeur ",x," avant l'appel de la fonction")

    mise_au_carre()

    print("x a comme valeur ",x," après l'appel de la fonction\n")


input("Appuyer sur ENTREE")

09° Testez ce code : la fonction parvient-elle à modifier maintenant à modifier la variable x du corps du programme ?

Comme vous le voyez, la fonction ne communique pas avec le corps du programme via un return mais en modifiant directement le contenu de la variable.

variables globales

Pourquoi ne peut pas abuser de cette méthode ? Plusieurs raisons à cela :

Résumé

  • Chaque partie du programme possède son propre espace des noms : partie principale et les fonctions.
  • Une variable du programme principal peut être lue mais pas modifiée dans les fonctions.
  • Une variable interne à une fonction n'a pas d'existence en dehors de cette fonction.
  • Les paramètres recoivent les données par valeur pas par identifiant. Les variables paramètres ne sont donc que des copies des arguments transmis : modifier les paramètres ne modifira pas la variable transmise en argument.
  • On peut communiquer avec l'extérieur grace au return. Si vous voulez transmettre plus d'un résultat, il faudra transmettre un tuple ou une liste par exemple.
  • On peut permettre à une fonction de contrôler une variable du programme principal à l'aide du mot-clé global.
variables locales

Attention, la notion de programme principal n'a pas beaucoup de sens : nous devrions dire le programme qui a lancé l'appel de la fonction. Et comme une fonction peut lancer une fonction, ca devient vite un jeu des poupées russes.

3 - Fonctions et objet

Pour compléter un peu la gestion de code à l'aide des fonctions, il nous reste à voir comment fonctions et objets interagissent en Python. Si vous regardez en arrière, vous pourrez voir que pour l'instant nous n'avons travaillé qu'avec des variables contenant un contenu facilement stockable : un nombre, un string... Je les nommerai ici des variables "simples" ou variables "typées": les variables affichant directement un contenu et non pas une référence vers d'autres adresses. En gros, elles contiennent un String, un Integer, un Float...

Attention, ce terme n'est aucunement un terme officiel de l'informatique. Mais distinguer les variables 'typées' et les variables 'références-objet' permet de facilement comprendre ce que chaque variable contient.

Dans les deux cas, la variable n'est rien d'autres qu'une adresse vers une zone de la mémoire

Néanmoins, dans le cas d'une variable 'simple' ou 'de structure', le contenu est facile à afficher : un nombre, une chaîne de caractère ... Avec un print, on peut donc obtenir directement le contenu.

Dans le cas d'une variable 'référence-objet' ou 'variable de référence', l'adresse mémoire contient l'inventaire des toutes les variables et méthodes attachés à cet objet. On ne peut donc pas directement l'afficher. Lorsqu'on tape le nom de la variable, on aura simplement la référence de l'objet, pas de contenu affichable.

Nous allons commencer avec un objet que nous avons déjà rencontré, la liste.

Deuxième rencontre avec les listes ?

Qu'est qu'une liste ? C'est une suite ordonnée et modifiable (mutable) d'objets ou variables quelconques.

Les éléments d'une liste sont séparés par des virgules et la définition de la liste commence avec [ et s'arrête avec ].

 maListe = [1,'a',45.2,"bonjour",'b']  crée une liste de 5 éléments contenant l'integer 1, le char a, le float 45.2, le string bonjour et le char b.

Si on veut afficher une liste, print(maListe) fonctionne.

>>> maListe = [1,'a',45.2,"bonjour",'b']

>>> print(maListe)

[1,'a',45.2,'bonjour','b']

Si on veut connaitre le nombre d'éléments dans une liste, len(maListe) fonctionne.

>>> maListe = [1,'a',45.2,"bonjour",'b']

>>> len(maListe)

5

Si on veut accéder à l'un des éléments d'une liste, on tapera maListe[2], mais attention le premier élément est l'élément 0.

Si on demande print(maListe[2]) avec maListe = [1,'a',45.2,"bonjour",'b'], on obtient :

45.2

Si on veut accéder à un ensemble d'éléments d'une liste, on tapera ma_liste[1:4], et on aura les éléments 1,2 et 3 (car on a une commande du type i<4).

Si on demande print(ma_liste[1:4]) avec ma_liste = [1, 'a', 45.2,"bonjour", 'b'], on obtient :

['a',45.2,'bonjour']

Pour parcourir une liste, il suffit d'utiliser un for.

for i in ma_liste :

    print(i)

Et on obtient alors

1

a

45.2

bonjour

b

C'est à partir d'ici qu'on rajoute quelques méthodes par rapport à la première fois :

Si on veut rajouter un élément à la fin de la liste ma_liste, on utilise ma_liste.append(x) où x est l'élément à rajouter.

Si on utilise ma_liste.append('nouveau') avec ma_liste = [1,'a',45.2,"bonjour",'b'], on obtient :

['a',45.2,'bonjour','b','nouveau']

Mais on peut utiliser également un simple + entre deux listes. Attention néanmoins : on crée alors une nouvelle liste qui n'aura pas la même adresse/référence/id que la précédente contrairement à l'utilisation de la méthode append.

Si on utilise ma_liste = ma_liste + ['nouveau'] avec ma_liste = [1,'a',45.2,"bonjour",'b'], on obtient également :

['a',45.2,'bonjour','b','nouveau']

Par contre, cette fois, on a recréé une liste qui porte le même nom : ce n'est pas la même liste au niveau de son id.

Si on veut rajouter un élément à une position particulière i dans la liste, on utilise ma_liste.insert(i,x) où x est l'élément à rajouter et i l'index dans la liste.

Si on utilise ma_liste.insert(1,'nouveau') avec ma_liste = [1,'a',45.2,"bonjour",'b'], on obtient :

['a','nouveau',45.2,'bonjour','b','nouveau']

Souvenez-vous que l'index du premier élément est 0, pas 1.

Si i dépasse l'index, x sera ajouté en fin de liste.

Si on veut supprimer un élément x, on utilise ma_liste.remove(x) où x est l'élément à supprimer dans la liste.

Si on utilise ma_liste.remove('nouveau') avec ma_liste = [1,'nouveau','a',45.2,"bonjour",'b','nouveau'], on obtient :

[1,'a',45.2,"bonjour",'b','nouveau']

On remarquera donc qu'il ne supprime que le premier élément 'nouveau' rencontré. S'il y en a d'autres, il faudra faire d'autres remove.

Justement, si on connaitre le nombre de fois où un élément x apparait dans une liste, on utilise ma_liste.count(x) où x est l'élément à surveiller dans la liste.

Si on utilise ma_liste.count('nouveau') avec ma_liste = [1,'nouveau','a',45.2,"bonjour",'b','nouveau'], on obtient :

2

Enfin, si on utilise ma_liste.sort(), on ordonne les éléments de la liste ma_liste.

Voilà pour le complément. Essayons maintenant de créer une fonction qui

#!/usr/bin/env python

# -*- coding: utf-8 -*-


def rajouter_chaine(truc_a_rajouter):

    try:

        x = str(truc_a_rajouter)

        if x.isnumeric():

            une_liste = une_liste+[truc_a_rajouter]

            return(1)

        elif x.isalpha():

            une_liste.insert(0,truc_a_rajouter)

            return(1)

        else:

            return(0)

    except:

        return(0)


une_liste = ["physique-chimie", "math","français"]

print(une_liste)

rajouter_chaine("12")

print(une_liste)

rajouter_chaine("EPS")

print(une_liste)

rajouter_chaine([15,45])

print(une_liste)

input("pause")

La grosse particularité du code : on tente de modifier la liste une_liste à l'intérieur de la fonction sans avoir déclarer ma_liste en global !

11° Utiliser le code pour voir s'il provoque une erreur, s'il fonctionne ou s'il ne fonctionne pas.

...CORRECTION...

Le programme ne provoque pas d'erreur mais il ne fonctionne pas. Pourquoi ?

Tout part du signe = et de l'affectation qui en découle. Dans cette fonction, on rajoute le nom une_liste dans l'espace des noms de la fonction. On ne travaille donc pas sur le une_liste de l'espace des noms du corps du programme mais sur une variable locale qui porte le même nom.

Rien de nouveau. On ne peut pas modifier le contenu d'une variable depuis l'intérieur de la fonction sans utiliser le mot-clé global. Les objets possèdent néanmoins des méthodes (des fonctions embarquées propre à l'objet) et certaines de ces méthodes permettent de modifier l'objet (rajouter ou détruire des choses...). Regardons ce qu'on peut faire en associer ces méthodes modifant les objets et les fonctions.

12° Remplacer simplement la ligne une_liste = une_liste+[x] par l'autre façon de rajouter un élément : la méthode append, soit une_liste.append(x). Testez. Que constatez-vous ?

Etrange non ? Cette fois, ça fonctionne. EPS est bien rajouté en début de liste et 12 en fin de liste. Cette fois, nous n'avons pas utilisé d'affectation (=), et la liste une_liste reste donc bien la référence de la variable globale. En effet, lorsqu'on tape une_liste., on va simplement lire l'adresse (ou référence) de la variable, sans la modifier. Et ça, on a le droit en Python.

Si on regarde le code utilisé (voir ci-dessous), on voit bien qu'on utilise que des méthodes sur la liste et jamais la moindre affectation directe :

        x = str(truc_a_rajouter)

        if x.isnumeric():

            une_liste.append(truc_a_rajouter)

            return(1)

        elif x.isalpha():

            une_liste.insert(0,truc_a_rajouter)

            return(1)

        else:

            return(0)


Et voilà : les fonctions fonctionnent avec les 'variables-objets' comme avec les 'variables simples' mais une 'variable-objet' contient l'adresse de l'objet. On peut donc utiliser directement la variable d'un objet déclaré dans le corps du programme avec les méthodes. Tant qu'on utilise pas d'affectation, tout va bien.

Il faudra néanmoins veiller à ne pas trop utiliser cette façon de faire, car il suffit de modifier le nom de variable dans le programme principal et les fonctions ne fonctionneront plus (sauf à penser à y modifier les noms aussi).

Modifier un objet de façon plus propre

Si on doit écrire ce code de façon un peu plus propre, il vaut mieux faire passer la liste en argument, la liste est alors transmise en référence pas simplement en valeur : on donne bien l'adresse de la liste en mémoire et pas juste une copie des valeurs.

#!/usr/bin/env python

# -*- coding: utf-8 -*-


def rajouter_chaine(liste_fonction, truc_a_rajouter):

    try:

        x = str(truc_a_rajouter)

        if x.isnumeric():

            liste_fonction.append(truc_a_rajouter)

            return(1)

        elif x.isalpha():

            liste_fonction.insert(0,truc_a_rajouter)

            return(1)

        else:

            return(0)

    except:

        return(0)


une_liste = ["physique-chimie", "math","français"]

printune_liste)

rajouter_chaine(une_liste,"12")

print(une_liste)

rajouter_chaine(une_liste,"EPS")

print(une_liste)

rajouter_chaine(une_liste,[15,45])

print(une_liste)

input("pause")

Quelle différence ? Et bien, on envoie la liste en argument : le paramètre de la fonction reçoit donc ici l'adresse de la liste et le place dans une variable interne qui peut porter le nom qu'on veut. Ainsi, on peut changer le nom de la liste dans le programme ou dans la fonction et ça continuera à fonctionner.

Et voilà l'intéret de tout ceci : on peut transmettre l'adresse d'un objet de fonction en fonction et parvenir à bien modifier l'objet en définitive, même si le nom de la variable qui contient l'adresse n'a plus rien à voir avec le nom d'origine : on pointe bien vers la bonne adresse mémoire.

13° Utiliser le code suivant pour vous en persuader :

#!/usr/bin/env python

# -*- coding: utf-8 -*-


def rajouter_chaine(liste_fonction, truc_a_rajouter):

    x = str(truc_a_rajouter)

    liste_fonction.append(x)


def autre_fonction(encore_une_liste,x):

    rajouter_chaine(encore_une_liste, x)


une_liste = ["physique-chimie", "math","français"]

print(une_liste)

autre_fonction(une_liste,"12")

print(une_liste)

autre_fonction(une_liste,"EPS")

print(une_liste)

autre_fonction(une_liste,[15,45])

print(une_liste)

input("pause")

On résume :

Resumé pour les variables :

Espace des noms :

On peut donner le même nom à des variables situées dans des structures différentes :

  • x déclarée dans le corps du programme,
  • x déclarée dans fonction_1 et
  • x dans fonction_3

désignent trois entités différentes ayant chacune une existence dans leur espace des noms respectifs.

variables locales

Visibilité d'une variable interne à une fonction :

Une variable d'une fonction n'est pas visible du corps du programme ou d'une autre fonction.

portée depuis les fonctions

Visibilité d'une variable du corps du programme :

Une variable du corps du programme est visible des fonctions rattachées mais pas modifiable depuis les fonctions.

variables locales

Une variable n'est modifiable que dans la structure où elle est créée (x de la fonction_1 n'est pas modifiable dans le corps du programme).

Si on veut modifier dans une fonction une variable du corps principal, il faut utiliser le mot-clé global dans la fonction.

Cas particulier des variables désignant un objet :

La différence avec les 'variables-simples' est que la 'variable-objet' ne renvoie pas le contenu mais la référence de l'objet.

On peut donc agir sur un objet du programme principal en utilisant une méthode de la classe de l'objet pourvu que cette méthode modifie réellement directement le contenu de l'objet.

Et bien voilà, avec tout cela, nous avez les prérequis pour créer votre projet petit jeu vidéo avec Tk et les Canvas. C'est un chapitre un peu plus loin.

4 - Mini-projet

Pour consolider tout ceci, je vous propose d'analyser notre simili programme de Simon. Avec un clic gauche sur un carré, vous mémorisez son activation. Avec un clic droit vous lancez la séquence des clics mémorisés.

Vous allez ainsi découvrir comment on peut mémoriser les actions de l'utilisateur, de façon à pouvoir refaire la même séquence. Ou vérifier que le joueur parvient à refaire la même séquence. On en profitera pour refaire l'exercice de la lecture d'un code préexistant. Une tâche pas toujours facile.

Les dernières questions vous permettront de comprendre le code ci-dessous.

#!/usr/bin/env python

# -*- coding: utf-8 -*-

from tkinter import *

from PIL import Image as Img

from PIL import ImageTk


# - - - - - - - - - - - - - - - - - -

# Variables globales

# - - - - - - - - - - - - - - - - - -

liste = ['Z','z']

indice = 0

animation = False


# - - - - - - - - - - - - - - - - - -

# Déclaration des fonctions

# - - - - - - - - - - - - - - - - - -


def lancement(event) :

    global indice

    global animation

    animation = True

    indice = 0

    animer()


def animer() :

    global indice

    global liste

    global animation

    # On place dans action l'action à faire, codée à l'aide d'un caractère

    action = liste[indice]

    # "A" active la 1er case, "a" la désactive

    # "B" active la 2e case, "b" la désactive

    # "C" active la 3e case, "c" la désactive

    # "D" active la 4e case, "d" la désactive

    # "Z" active les 4 cases, "z" les désactive

    if action == "A" and animation == True :

        caseAON()

    elif action == "a" and animation == True :

        caseAOFF()

    elif action == "B" and animation == True :

        caseBON()

    elif action == "b" and animation == True :

        caseBOFF()

    elif action == "C" and animation == True :

        caseCON()

    elif action == "c" and animation == True :

        caseCOFF()

    elif action == "D" and animation == True :

        caseDON()

    elif action == "d" and animation == True :

        caseDOFF()

    elif action == "Z" and animation == True :

        caseAON()

        caseBON()

        caseCON()

        caseDON()

    elif action == "z" and animation == True :

        caseAOFF()

        caseBOFF()

        caseCOFF()

        caseDOFF()

    # On augmente l'indice de 1

    indice = indice+1

    # Le test suivant permet de voir si on doit continuer l'animation ou si on a fini

    if indice < len(liste) :

        fen_princ.after(500,animer)

    else :

        indice = 0

        liste = ['Z','z']

        animation = False

    # Print permettant de comprendre ce que fait le code, mode debug

    print(len(liste),' ', indice,' ', animation)


def changeA(event) :

    caseAON()

    liste.append('A')

    liste.append('a')


def retourA(event) :

    caseAOFF()


def changeB(event) :

    caseBON()

    liste.append('B')

    liste.append('b')


def retourB(event) :

    caseBOFF()


def changeC(event) :

    caseCON()

    liste.append('C')

    liste.append('c')


def retourC(event) :

    caseCOFF()


def changeD(event) :

    caseDON()

    liste.append('D')

    liste.append('d')


def retourD(event) :

    caseDOFF()

def caseAON() :

    imageA.configure(image = carreBleuTk2)


def caseAOFF() :

    imageA.configure(image = carreBleuTk)


def caseBON() :

    imageB.configure(image = carreVertTk2)


def caseBOFF() :

    imageB.configure(image = carreVertTk)


def caseCON() :

    imageC.configure(image = carreRougeTk2)


def caseCOFF() :

    imageC.configure(image = carreRougeTk)


def caseDON() :

    imageD.configure(image = carreJauneTk2)


def caseDOFF() :

    imageD.configure(image = carreJauneTk)


# - - - - - - - - - - - - - - - - - -

# Création de la fenêtre et des objets associés la fenêtre

# - - - - - - - - - - - - - - - - - -


fen_princ = Tk()

fen_princ.geometry("500x500")


# Création des images de base


carreBleu = Img.new("RGB", (100,100), (0,0,200))

carreBleuTk = ImageTk.PhotoImage(carreBleu)


carreVert = Img.new("RGB", (100,100), (0,200,0))

carreVertTk = ImageTk.PhotoImage(carreVert)


carreRouge = Img.new("RGB", (100,100), (200,0,0))

carreRougeTk = ImageTk.PhotoImage(carreRouge)


carreJaune = Img.new("RGB", (100,100), (200,200,0))

carreJauneTk = ImageTk.PhotoImage(carreJaune)


carreBleu2 = Img.new("RGB", (100,100), (75,75,250))

carreBleuTk2 = ImageTk.PhotoImage(carreBleu2)


carreVert2 = Img.new("RGB", (100,100), (75,250,75))

carreVertTk2 = ImageTk.PhotoImage(carreVert2)


carreRouge2 = Img.new("RGB", (100,100), (250,75,75))

carreRougeTk2 = ImageTk.PhotoImage(carreRouge2)


carreJaune2 = Img.new("RGB", (100,100), (250,250,0))

carreJauneTk2 = ImageTk.PhotoImage(carreJaune2)


# Création et placement des widget Label


imageA = Label(fen_princ, image = carreBleuTk)

imageA.place(x = 100, y = 100)


imageB = Label(fen_princ, image = carreVertTk)

imageB.place(x = 300, y = 100)


imageC = Label(fen_princ, image = carreRougeTk)

imageC.place(x = 100, y = 300)


imageD = Label(fen_princ , image = carreJauneTk)

imageD.place(x = 300, y = 300)


# - - - - - - - - - - - - - - - - - -

# Gestion des événements

# - - - - - - - - - - - - - - - - - -


imageA.bind( '<Button-1>', changeA )

imageA.bind( '<ButtonRelease-1>', retourA )


imageB.bind( '<Button-1>', changeB )

imageB.bind( '<ButtonRelease-1>', retourB )


imageC.bind( '<Button-1>', changeC )

imageC.bind( '<ButtonRelease-1>', retourC )


imageD.bind( '<Button-1>', changeD )

imageD.bind( '<ButtonRelease-1>', retourD )


imageA.bind( '<ButtonRelease-3>', lancement )

imageB.bind( '<ButtonRelease-3>', lancement )

imageC.bind( '<ButtonRelease-3>', lancement )

imageD.bind( '<ButtonRelease-3>', lancement )


# - - - - - - - - - - - - - - - - - -

# Bouclage de la fenêtre fen_princ

# - - - - - - - - - - - - - - - - - -


fen_princ.mainloop()

Pour comprendre ce code, il va falloir le lire dans un certain ordre.

14° Regardons le début du programme. Quels sont les modules importés en totalité ? Parmi ceux-ci quels sont ceux qui sont importées sans avoir besoin de les nommer pour utiliser leur contenu ?

#!/usr/bin/env python

# -*- coding: utf-8 -*-

from tkinter import *

from PIL import Image as Img

from PIL import ImageTk


# - - - - - - - - - - - - - - - - - -

# Variables globales

# - - - - - - - - - - - - - - - - - -

liste = ['Z','z']

indice = 0

animation = False


...CORRECTION...

On pourra utiliser tous les modules de la bibliothèque tkinter sans avoir besoin de les nommer.

On pourra utiliser le module Image de la bibliothèque Pillow en utilisant Img.

On pourra utiliser le module ImageTk de la bibliothèque Pillow en utilisant ImageTk.

15° Pouvez-vous donner le rôle des trois variables globales ?

...CORRECTION...

Aucune idée. La seule manière de comprendre ce programme est de se mettre dans la peau de la personne qui a réalisé le programme. Pourquoi ?

Parce qu'il n'y a aucune documentation ou commentaire.

Du coup, le code est certainement fonctionnel mais peu réutilisable...

Bon, ce n'est pas clair au niveau des variables globales. Pareil pour les fonctions : il n'y a aucun commentaire non plus. Mais, tentons de voir ce que cela donne au niveau de la création des fenêtres.

16° Regardons la création de la fenêtre. Quels sont les 4 Labels ? Que contiennent-ils initialement au démarrage du programme ? Avec quelle méthode sont-ils positionnés dans la fenêtre ?

...CORRECTION...

Les 4 labels sont imageA, imageB, imageC et imageD.

Les Labels contiennent initialement respectivement les images carreBleuTk, carreVertTk, carreRougeTk et carreJauneTk.

Les Labels sont positionnés avec la méthode place.

17° Regardez la partie sur la gestion des événements. Quelle fonction se lance si on clique sur le carré A avec le bouton gauche de la souris (le Button-1) ? Lorsqu'on relache (release) le bouton gauche de la souris ? Avec le relachement du bouton droit de la souris (le Button-3) ?

# - - - - - - - - - - - - - - - - - -

# Gestion des événements

# - - - - - - - - - - - - - - - - - -


imageA.bind( '<Button-1>', changeA )

imageA.bind( '<ButtonRelease-1>', retourA )


imageB.bind( '<Button-1>', changeB )

imageB.bind( '<ButtonRelease-1>', retourB )


imageC.bind( '<Button-1>', changeC )

imageC.bind( '<ButtonRelease-1>', retourC )


imageD.bind( '<Button-1>', changeD )

imageD.bind( '<ButtonRelease-1>', retourD )


imageA.bind( '<ButtonRelease-3>', lancement )

imageB.bind( '<ButtonRelease-3>', lancement )

imageC.bind( '<ButtonRelease-3>', lancement )

imageD.bind( '<ButtonRelease-3>', lancement )


...CORRECTION...

Button-1 est associé sur A à la fonction changeA.

Le relachement du Button-1 (gauche) est associé sur A à la fonction retourA.

Le relachement du Button-3 (droit) est associé sur tous les carrés à la fonction lancement.

Comme signalé plus haut, le reste du code est beaucoup plus cryptique... Pourquoi ? Tout simplement car le code manque de documentation. Du coup, on ne sait pas vraiment ce qu'il est censé faire.

Lors de la réalisation de vos projets, pensez donc à ceux qui vont lire vos codes !

Je vous propose donc le même code mais un peu mieux documenté.

Pour rajouter des commentaires sur une ligne, vous savez déjà utiliser le dièse #.

Mais pour indiquer des commentaires plus long en introduction des fonctions, vous pouvez utiliser en ouverture 3 doubles aposthrophes de suite """ et la même chose en fermeture.

Pour la suite, nous allons travailler avec le même code, mais, cette fois, il va être correctement documenté.

#!/usr/bin/env python

# -*- coding: utf-8 -*-

from tkinter import *

from PIL import Image as Img

from PIL import ImageTk


# - - - - - - - - - - - - - - - - - -

# Variables globales

# - - - - - - - - - - - - - - - - - -


liste = ['Z','z']

"""

La variable liste est une liste qui contient l'ensemble des actions que l'utilisateur à réaliser sur les carrés colorés.

Valeurs possibles dans cette liste :

'Z' permet d'activer les 4 carrés (les rendre plus clairs)

'z' permet de remettre les 4 carrés d'origine (les carrés plus foncés)

De la même façon :

'A' et 'a' permettent respectivement d'avoir le carré bleu en bleu clair et bleu foncé.

'B' et 'b' permettent respectivement d'avoir le carré vert en vert clair et vert foncé.

'C' et 'c' permettent respectivement d'avoir le carré rouge en rouge clair et rouge foncé.

'D' et 'd' permettent respectivement d'avoir le carré jaune en jaune clair et jaune foncé.

Ainsi, si liste = ['D','d','A','a'], cela veut dire qu'on veut simuler un clic sur le carré jaune puis un clic sur le carré bleu.

"""


indice = 0

"""

La variable indice contient le numéro d'index de l'action à effectuer dans la liste d'actions ci-dessus. Elle permet de gérer l'animation lorsqu'on restitue à l'écran la suite de clics effectués sur les carrés par l'utilisateur.

"""


animation = False

"""

La variable animation passe à True lorsqu'on veut voir la séquence de clics effectués par l'utilisateur et repasse à False à la fin de l'animation.

"""


# - - - - - - - - - - - - - - - - - -

# Déclaration des fonctions

# - - - - - - - - - - - - - - - - - -


def lancement(event) :

    """

    Cette fonction EVENEMENT est activée lorsqu'on relache le bouton droit de la souris au dessus d'un carré.

    Elle lance la fonction animer après avoir correctement configuré les variables indice et animation.

    Elle nécessite les variables globales indice et animation.

    """

    global indice

    global animation

    animation = True

    indice = 0

    animer()


def animer() :

    """

    Cette fonction réalise une action sur les carrés (effet cliqué ou effet relaché).

    Elle va chercher dans l'action stockée dans liste[indice].

    Elle réalise l'action codée sous forme d'un caractère ('A','a' ...)

    Puis se relance tant que la variable animation est True.

    Elle nécessite les variables globales liste,indice et animation.

    """

    global indice

    global liste

    global animation

    # On place dans action l'action à faire, codée à l'aide d'un caractère

    action = liste[indice]

    # "A" active la 1er case, "a" la désactive

    # "B" active la 2e case, "b" la désactive

    # "C" active la 3e case, "c" la désactive

    # "D" active la 4e case, "d" la désactive

    # "Z" active les 4 cases, "z" les désactive

    if action == "A" and animation == True :

        caseAON()

    elif action == "a" and animation == True :

        caseAOFF()

    elif action == "B" and animation == True :

        caseBON()

    elif action == "b" and animation == True :

        caseBOFF()

    elif action == "C" and animation == True :

        caseCON()

    elif action == "c" and animation == True :

        caseCOFF()

    elif action == "D" and animation == True :

        caseDON()

    elif action == "d" and animation == True :

        caseDOFF()

    elif action == "Z" and animation == True :

        caseAON()

        caseBON()

        caseCON()

        caseDON()

    elif action == "z" and animation == True :

        caseAOFF()

        caseBOFF()

        caseCOFF()

        caseDOFF()

    # On augmente l'indice de 1

    indice = indice+1

    # Le test suivant permet de voir si on doit continuer l'animation ou si on a fini

    if indice < len(liste) :

        fen_princ.after(500,animer)

    else :

        indice = 0

        liste = ['Z','z']

        animation = False

    # Print permettant de comprendre ce que fait le code, mode debug

    print(len(liste),' ', indice,' ', animation)


def changeA(event) :

    """

    Cette fonction EVENEMENT est activée lorsqu'on clique sur le carré imageA avec le bouton gauche.

    Elle rend le carré plus clair via l'appel à caseAON().

    Elle rajoute dans liste les strings 'A' et 'a' simulant un clic sur le carré imageA.

    """

    caseAON()

    liste.append('A')

    liste.append('a')


def retourA(event) :

    """

    Cette fonction EVENEMENT est activée lorsqu'on cesse de cliquer sur le carré imageA.

    Elle rend le carré plus clair via l'appel à caseAOFF().

    """

    caseAOFF()


def changeB(event) :

    """

    Cette fonction EVENEMENT est activée lorsqu'on clique sur le carré imageB avec le bouton gauche..

    Elle rend le carré plus clair via l'appel à caseBON().

    Elle rajoute dans liste les strings 'B' et 'b' simulant un clic sur le carré imageB.

    """

    caseBON()

    liste.append('B')

    liste.append('b')


def retourB(event) :

    """

    Cette fonction EVENEMENT est activée lorsqu'on cesse de cliquer sur le carré imageB.

    Elle rend le carré plus clair via l'appel à caseBOFF().

    """

    caseBOFF()


def changeC(event) :

    """

    Cette fonction EVENEMENT est activée lorsqu'on clique sur le carré imageC avec le bouton gauche.

    Elle rend le carré plus clair via l'appel à caseCON().

    Elle rajoute dans liste les strings 'C' et 'c' simulant un clic sur le carré imageC.

    """

    caseCON()

    liste.append('C')

    liste.append('c')


def retourC(event) :

    """

    Cette fonction EVENEMENT est activée lorsqu'on cesse de cliquer sur le carré imageC.

    Elle rend le carré plus clair via l'appel à caseCOFF().

    """

    caseCOFF()


def changeD(event) :

    """

    Cette fonction EVENEMENT est activée lorsqu'on clique sur le carré imageD .

    Elle rend le carré plus clair via l'appel à caseDON().

    Elle rajoute dans liste les strings 'D' et 'd' simulant un clic sur le carré.

    """

    caseDON()

    liste.append('D')

    liste.append('d')


def retourD(event) :

    """

    Cette fonction EVENEMENT est activée lorsqu'on cesse de cliquer sur le carré D.

    Elle rend le carré plus clair via l'appel à caseDOFF().

    """

    caseDOFF()

def caseAON() :

    """ Cette fonction affecte l'image claire au label imageA."""

    imageA.configure(image = carreBleuTk2)


def caseAOFF() :

    """ Cette fonction affecte l'image foncée au label imageA."""

    imageA.configure(image = carreBleuTk)


def caseBON() :

    """ Cette fonction affecte l'image claire au label imageB."""

    imageB.configure(image = carreVertTk2)


def caseBOFF() :

    """ Cette fonction affecte l'image foncée au label imageB."""

    imageB.configure(image = carreVertTk)


def caseCON() :

    """ Cette fonction affecte l'image claire au label imageC."""

    imageC.configure(image = carreRougeTk2)


def caseCOFF() :

    """ Cette fonction affecte l'image foncée au label imageC."""

    imageC.configure(image = carreRougeTk)


def caseDON() :

    """ Cette fonction affecte l'image claire au label imageD."""

    imageD.configure(image = carreJauneTk2)


def caseDOFF() :

    """ Cette fonction affecte l'image foncée au label imageD."""

    imageD.configure(image = carreJauneTk)


# - - - - - - - - - - - - - - - - - -

# Création de la fenêtre et des objets associés la fenêtre

# - - - - - - - - - - - - - - - - - -


fen_princ = Tk()

fen_princ.geometry("500x500")


# Création des images de base


carreBleu = Img.new("RGB", (100,100), (0,0,200))

carreBleuTk = ImageTk.PhotoImage(carreBleu)


carreVert = Img.new("RGB", (100,100), (0,200,0))

carreVertTk = ImageTk.PhotoImage(carreVert)


carreRouge = Img.new("RGB", (100,100), (200,0,0))

carreRougeTk = ImageTk.PhotoImage(carreRouge)


carreJaune = Img.new("RGB", (100,100), (200,200,0))

carreJauneTk = ImageTk.PhotoImage(carreJaune)


carreBleu2 = Img.new("RGB", (100,100), (75,75,250))

carreBleuTk2 = ImageTk.PhotoImage(carreBleu2)


carreVert2 = Img.new("RGB", (100,100), (75,250,75))

carreVertTk2 = ImageTk.PhotoImage(carreVert2)


carreRouge2 = Img.new("RGB", (100,100), (250,75,75))

carreRougeTk2 = ImageTk.PhotoImage(carreRouge2)


carreJaune2 = Img.new("RGB", (100,100), (250,250,0))

carreJauneTk2 = ImageTk.PhotoImage(carreJaune2)


# Création et placement des widget Label


imageA = Label(fen_princ, image = carreBleuTk)

imageA.place(x = 100, y = 100)


imageB = Label(fen_princ, image = carreVertTk)

imageB.place(x = 300, y = 100)


imageC = Label(fen_princ, image = carreRougeTk)

imageC.place(x = 100, y = 300)


imageD = Label(fen_princ , image = carreJauneTk)

imageD.place(x = 300, y = 300)


# - - - - - - - - - - - - - - - - - -

# Gestion des événements

# - - - - - - - - - - - - - - - - - -


imageA.bind( '<Button-1>', changeA )

imageA.bind( '<ButtonRelease-1>', retourA )


imageB.bind( '<Button-1>', changeB )

imageB.bind( '<ButtonRelease-1>', retourB )


imageC.bind( '<Button-1>', changeC )

imageC.bind( '<ButtonRelease-1>', retourC )


imageD.bind( '<Button-1>', changeD )

imageD.bind( '<ButtonRelease-1>', retourD )


imageA.bind( '<ButtonRelease-3>', lancement )

imageB.bind( '<ButtonRelease-3>', lancement )

imageC.bind( '<ButtonRelease-3>', lancement )

imageD.bind( '<ButtonRelease-3>', lancement )


# - - - - - - - - - - - - - - - - - -

# Bouclage de la fenêtre fen_princ

# - - - - - - - - - - - - - - - - - -


fen_princ.mainloop()

18° Lancez le programme. Essayez le puis fermez le. Dans la console, tapez ensuite :

>>> help(animer)

Pas mal non ? Cela vous permettra de chercher facilement la documentation sur une fonction, pourvu qu'elle soit bien documentée !

>>> help(animer)

Help on function animer in module __main__:


animer()

    Cette fonction réalise une action sur les carrés (effet cliqué ou effet relaché).

    Elle va chercher dans l'action stockée dans liste[indice].

    Elle réalise l'action codée sous forme d'un caractère ('A','a' ...)

    Puis se relance tant que la variable animation est True.

    Elle nécessite les variables globales liste,indice et animation.

19° Que va contenir la liste si l'utilisateur clique deux fois de suite sur le carré bleu puis un fois sur le carré jaune ?

...CORRECTION...

La liste contient déja ['Z','z'] de base.

Si on clique sur le carré bleu, on obtient ['Z','z','A','a'].

Une deuxième fois, on obtient ['Z','z','A','a','A','a'].

En cliquant sur le jaune, on obtient ['Z','z','A','a','A','a','D','d'].

20° Localiser l'endroit où on augmente l'indice de l'action de 1. Aurait-on pu se passer de global ?

...CORRECTION...

Non, on utilise un signe =. Sans global, on aurait créer une variable locale qui porte le même nom que la variable indice définie au début du programme.

Comme vous le voyez, le problème des appels de base avec Tk est qu'on ne peut pas facilement transmettre de paramètres. D'où l'utilisation massive des variables globales pour l'instant. Je vous laisse avec ce dernier programme, testez le, modifiez le. Vous avez maintenant presque assez de connaissances pour créer un véritable jeu animé. C'est le but des chapitres "Interface graphique" 2, 3 et 4.