Infoforall

Python 09 : LE TANT QUE et le FOR nominatif

Nous allons utiliser le FOR numérique et nominatif ainsi que les tests SI pour réaliser ceci :

Vous verrez également les boucles WHILE et les tests TRY en Python.

Tout ceci sera appliqué à deux structures de données particulières :

1 - La boucle WHILE - TANT QUE

Bien, passons maintenant aux boucles WHILE : on peut demander au programme de faire plusieurs fois la même chose tant qu’une condition précise est respectée.

Tant que la condition donnée est vraie, le programme va lancer les instructions à l’intérieur de la structure. Il est donc possible que cette instruction ne se fasse jamais si la condition n’est pas vérifiée initialement.

Cette boucle s'utilise comme ceci :

while condition :

    Instruction 1 à répéter

    Instruction 2 à répéter

Tout ce qui est tabulé sera répété tant que la condition est vérifiée (True). Attention donc au copier-coller : si l’une des tabulations est mal encodée, ça peut modifier tout le programme…

Voilà un premier exemple qui vous montre comment demander une note tant que la note est supérieure à 20.

#!/usr/bin/env python

# -*-coding:Utf-8 -*


# Acquisition des donnees note 1

note1 = 21

while (note1>20) :

    note1 = input("Quelle est votre note (entière) ? ")

    note1 = int(note1)


print("Votre note est ", note1)

input("pause")

CLIQUEZ SUR LE PREMIER BOUTON :

Réponse Tapée >>>

note1 :

01° Pourquoi rentre-t-on forcément au moins une fois dans la boucle ? Quelle est la fonction qui transforme la chaîne de caractères en integer ? Modifier le programme pour qu’il vérifie en plus que la note soit bien supérieure à 0 : il faut rajouter une condition dans la condition du while en utilisant or).

...CORRECTION...

On rentre dans la boucle puisqu'on place arbitrairement 21 dans la variable note1. La condition du WHILE est donc vraie : 21 est bien strictement supérieur à 20.

La fonction int() permet de transformer le string de la réponse en entier.

Pour la condition double :

while (note1>20 or note1<0) :

Une autre façon de faire : on part d’un test True et on le rend False lorsqu’on estime qu’on peut sortir :

#!/usr/bin/env python

# -*-coding:Utf-8 -*


# Acquisition des donnees note 1

test = True

while test :

    note1 = input("Quelle est votre note (entière) ? ")

    note1 = int(note1)

    test = (note1<0 or note1>20) # Boolean


print("Votre note est ", note1)

input("pause")

Et si on veut vérifier que la chaîne tapée par l’utilisateur est bien un chiffre ?

Il faudrait utiliser la méthode isnumeric() : note1.isnumeric() renvoie True si tous les caractères de la chaîne note1 correspondent à des chiffres. Sinon (c’est à dire si au moins un caractère n’est pas un chiffre), elle renvoie False.

02° Utiliser le code donné juste au dessus en tapant n’importe quoi comme A ou b. Le Modifier en utilisant la méthode isnumeric pour éviter les erreurs et recommencer la saisie. Il faudra modifier test par exemple.

C’est bien beau mais les notes ne sont pas nécessairement des entiers. Nous aimerions savoir gérer les notes à virgules. Ah mais je sais : nous allons remplacer la fonction int par la fonction float dans le code de la question 01 : facile !

#!/usr/bin/env python

# -*-coding:Utf-8 -*


# Acquisition des donnees note 1

test = True

while test :

    note1 = input("Quelle est votre note (même pas entière !) ? ")

    if note1.isnumeric() :

        note1 = float(note1)

        if (note1>=0 and note1<=20) :

            test = False


print("Votre note est ", note1)

input("pause")

03° Essayons le code donné ci-dessus : Tapez donc une note de 4. Ca fonctionne. Bien, tentons 14.5. Et non. Normal, le point n’est pas un caractère numérique. On ne rentre donc jamais dans le if. Et on ne sort jamais de la boucle du coup…

Là, c’est un peu compliqué, donc je vous donne une solution avec des commentaires.

Le plus simple est de faire un code qui fonctionne très bien si l’utilisateur rentre bien une note (un truc comme 14 ou 12.72 par exemple).

#!/usr/bin/env python

# -*-coding:Utf-8 -*


# Acquisition des donnees note 1

test = True

while test :

    note1 = input("Quelle est votre note (même pas entière !) ? ")

    note1 = float(note1)

    if (note1>=0 and note1<=20) :

        test = False


print("Votre note est ", note1)

input("pause")

04° Essayons le code donné ci-dessus dans IDLE (cela nous permettra de voir le type d’erreur créée : Tapez donc une note de 4. Ca fonctionne. Bien, tentons 14.5. Bien. 14,5 ? Et non. Normal, la virgule ne permet pas de créer un float en informatique. C’est le point qu’il faut utiliser. Recommencez avec a ? Et oui, encore une erreur : une ValueError.

En gros, on ne peut pas s’en sortir très facilement :

2 - Le test TRY et l'EXCEPT en cas d'échec

La manière la plus courte de régler le problème est d’attraper l’exception (l’erreur) et de lui dire ce qu’il doit faire si ça n’a pas marché.

La codification n’est pas compliquée mais nous ne l’avons pas vu.

Nous allons tenter de faire une action. Voir la ligne avec try :.

Si l’action échoue en provoquant une exception ValueError (voir la ligne avec except ValueError :), on lui dit comment réagir : ici, je ne complique pas : je lui dis juste de mettre note1 à la valeur –1 si l’entrée clavier était illisible avec float.

#!/usr/bin/env python

# -*-coding:Utf-8 -*


# Acquisition des donnees note 1

test = True

while test :

    note1 = input("Quelle est votre note (même pas entière !) ? ")


    try:

        note1 = float(note1)

    except ValueError:

        note1 = -1


    if (note1>=0 and note1<=20) :

        test = False


print("Votre note est ", note1)

input("pause")

05° Si on ne peut pas convertir la note en float (nombre à virgules), que place-t-on dans note1 ? Cela permet-il de recommencer la boucle ?

3 - Lecture d’éléments avec une boucle FOR nominative

Nous avons vu que Python est un langage performant : on peut lui ordonner de lire un à un les éléments de base constitutifs d’un objet (noté maSequence ci-dessous) plus complexe. On avait utilisé pour cela une boucle FOR numérique (avec un compteur) : for compteur in range(....

Mais, on peut faire encore plus simple avec la boucle FOR nominative : on lui dit de faire des actions sur chaque element contenu dans maSequence :

Premier exemple : lecture d'un string

maSequence = "Hello World !" # On crée un string

for element in maSequence : # Pour chaque caractère-element contenu dans maSequence

    print(element) # Affiche element à l'écran

input("pause")

Voir l'exécution :

maSequence :

element :

>>>

On notera toujours la présence du : en pour indiquer la fin de la condition du test.

Hello World !

H

e

l

l

o

 

W

o

r

l

d

 

!

Qu'est qu'un string ? C'est une structure de données :

Une fois qu'on a défini le contenu d'un string, on ne peut plus y toucher. Par contre, on peut créer un autre string et le stocker dans une variable qui porte le même nom.

Passons à une petite application pratique de la lecture d'un string avec un for nominatif. Nous voudrions réaliser un pendu.

Le mot à trouver sera enregistré dans la variable trucSecret.

Nous voudrions donc afficher un ensemble de caractères où les lettres sont remplacées par des étoiles *, sauf si le caractère est un espace ou un tiret. Dans ce cas, on laisse l'espace ou le tiret.

Exemple : Le string  Bonjour tout le monde  doit faire afficher  ****** **** ** ***** .

06° Le code ci-dessous permet d'afficher à l'identique le contenu de trucSecret en lisant et affichant ces caractères un à un. Modifier le programme pour remplacer les caractères par des étoiles, sauf quand le caractère est un espace ou un tiret.

#!/usr/bin/env python

# -*-coding:Utf-8 -*


trucSecret = "Bonjour tout le monde" # On crée le string maChaine

for element in trucSecret : # Pour chaque caractère-element contenu dans trucSecret

    print(element, end="") # Affiche element à l'écran

print()

input("pause")

C'est quoi end="" ? Cela permet de remplacer le passage à la ligne par défaut à la fin d'un print par ... rien. Le prochain print se fera donc sur la même ligne. C'est d'ailleurs pour cela que j'ai rajouté un print() vide après la boucle. Avec celui-ci, on passe bien à la ligne puisqu'on ne précise pas d'end particulier.

...CORRECTION...

#!/usr/bin/env python

# -*-coding:Utf-8 -*


trucSecret = "Bonjour tout le monde" # On crée le string trucSecret

for element in trucSecret : # Pour chaque caractère-element contenu dans trucSecret

    if (element !=' ' and element !='-') :

        print('*', end="") # Affiche * à l'écran

    else :

        print(element, end="") # Affiche element à l'écran

print()

input("pause")

On a maintenant un affichage composé d'étoile. Compliquons encore un peu : on voudrait maintenant demander en boucle à l'utilisateur quelle lettre il veut tester. Nous placerons sa réponse dans une variable laLettre. Si la lettre est présente, on affiche la chaîne étoilée mais on place la lettre au endroit où elle se situe dans le mot secret.

Exemple : Le string  Bonjour tout le monde  doit faire afficher  ****** **** ** ***** . Je veux savoir s'il y a des o, on doit alors afficher  *o**o** *o** ** *o*** .

07° Modifier le code ci-dessous de façon à ce qu'il gère correctement l'affichage après le choix de la lettre à tester.

#!/usr/bin/env python

# -*-coding:Utf-8 -*


trucSecret = "Bonjour tout le monde" # On crée le string trucSecret

for element in trucSecret : # Pour chaque caractère-element contenu dans trucSecret

    if (element !=' ' and element !='-') :

        print('*', end="") # Affiche * à l'écran

    else :

        print(element, end="") # Affiche element à l'écran

print()


while True :

    laLettre = input("Quelle lettre voulez-vous tester ?")

    for element in trucSecret : # Pour chaque caractère-element contenu dans trucSecret

        if (element !=' ' and element !='-') :

            print('*', end="") # Affiche * à l'écran

        else :

            print(element, end="") # Affiche element à l'écran

    print()


input("pause")

L'une des solutions :

...CORRECTION...

#!/usr/bin/env python

# -*-coding:Utf-8 -*


trucSecret = "Bonjour tout le monde" # On crée le string trucSecret

for element in trucSecret : # Pour chaque caractère-element contenu dans trucSecret

    if (element !=' ' and element !='-') :

        print('*', end="") # Affiche * à l'écran

    else :

        print(element, end="") # Affiche element à l'écran

print()


while True :

    laLettre = input("Quelle lettre voulez-vous tester ?")

    for element in trucSecret : # Pour chaque caractère-element contenu dans trucSecret

        if (element != ' ' and element != '-' and element != laLettre) :

            print('*', end="") # Affiche * à l'écran

        else :

            print(element, end="") # Affiche element à l'écran

    print()


input("pause")

Dernière chose : on peut tester la présence d’un caractère monCaractere dans un String maChaine à l’aide de :

    if monCaractere in maChaine :

Le test suivant est True :

    if 'o' in 'Bonjour tout le monde' :

Le test suivant est False :

    if 'z' in 'Bonjour tout le monde' :

Application : on veut remplacer les consonnes par des étoiles mais les voyelles seront inchangées.

Exemple : Le string  Bonjour tout le monde  doit faire afficher  *o**ou* *ou* *e *o**e .

chaine = ours tigre poule"

for lettre in chaine :

    if lettre in "AEIOUYaeiouy" : # lettre est une voyelle

        print(lettre, end="")

    else :

        print("*", end="")

print()

input("pause")

08° Une erreur d’inattention s’est glissée dans le programme ci-dessus. Modifiez le pour qu'il fonctionne correctement. Faire la dernière modification pour que les espaces et les tirets soient correctement gérés.

...CORRECTION...

chaine = "ours tigre poule"

for lettre in chaine :

    if lettre in "AEIOUYaeiouy -" : # lettre est une voyelle

        print(lettre, end="")

    else :

        print("*", end="")

print()

input("pause")

Voilà, c'est tout pour l’instant sur l’interaction entre string et boucle. Nous compléterons encore les choses par la suite. Dans l'activité suivante sur les strings, nous verrons un peu plus précisement l'ASCII et le fait que l'espace soit bien un caractère avec un numéro et pas un espace vide. Et nous verrons surtout qu'il existe une beaucoup de méthodes applicables aux strings.

Deuxième exemple : lecture d'une liste

Bon, et si l'objet n’est pas composé d’un ensemble uniforme de composants de base ? Essayons. Dans le code suivant, y est composé d’integers, d’un string et même d’un 3-uplets de données. Nous verrons, dans une autre activité, que y est ce qu’on appelle une liste : un ensemble ordonné d’éléments pouvant être différents les uns des autres.

maListe = [ 1, 2, 9, 8, "ah ah ah", 45, (45,45,45) ]

for element in maListe :

    print(element)

input("pause")

09° Alors ça fonctionne ou non ? Avons-nous trouvé une première limite à ce que Python sait faire ?

...CORRECTION...

Ca fonctionne très bien. Python affiche les éléments un à un.

1

2

9

8

ah ah ah

45

(45,45,45)

Troisième exemple : lecture des mots d'une phrase

On peut également énoncer les mots d’une phrase plutôt que les caractères d’un mot.

Pour cela, il nous faut un string phrase par exemple.

On utilisera la méthode split qui va créer une liste contenant comme éléments les bouts de la phrase : elle sépare la phrase en plusieurs éléments en utilisant le caractère séparateur fourni entre parenthèse. Si on ne fournit rien, le caractère séparateur par défaut est l'espace. Ca tombe bien, on sépare les mots par des espaces !

liste_mots = phrase.split()

Exemple avec une string phrase et la liste liste_mots correspondante :

phrase = "Bonjour le monde"

liste_mots = [ "Bonjour, "le", "monde" ]

10° Utiliser le code suivant pour vérifier que le programme parvient bien à séparer les mots de la phrase. Ca fonctionne ou non ? Est-ce parfait ?

phrase = "Désormais, nous allons pouvoir gérer des phrases, longues ou courtes."

liste_mots = phrase.split() # mots est la liste issue du "split" de phrase

print(liste_mots)

for mot in liste_mots : # pour chaque élément mot contenu dans mots

    print(mot) # on affiche mot

input("pause")

Normalement, on obtient :

['Désormais,', 'nous', 'allons', 'pouvoir', 'gérer', 'des', 'phrases,', 'longues', 'ou', 'courtes.']

Désormais,

nous

allons

pouvoir

gérer

des

phrases,

longues

ou

courtes.

Vous pouvez vous documenter facilement sur la méthode split. Une simple recherche « Python split » devrait vous amener vers de multiples sites vous offrant la documentation adéquate.

Bon, comment régler le problème de la virgule et du point qui reste là ? Une rapide inspection de split ne règle pas le problème : on peut faire ceci pour choisir la virgule comme paramètre séparateur :

mots = phrase.split(',')

Mais il faudra alors faire un deuxième passage puisqu’on ne peut utiliser qu’un caractère de séparation.

Deux solutions s’offrent à nous en réfléchissant un peu : on supprime toutes les virgules et autres séparateurs :

11° Résolution 1 : On supprime avant le split à l’aide de la méthode replace(old, new) qui permet de remplacer la chaîne de caractères old par la chaîne de caractères new. Nous allons habilement remplacer les virgules et points par … rien. En gros, cela va les supprimer :

phrase = "Désormais, nous allons pouvoir gérer des phrases, longues ou courtes."


phrase = phrase.replace(",","")

phrase = phrase.replace(".","")


mots = phrase.split()

for mot in mots :

        print(mot)

print(mots)


input("pause")

On voit que la liste mots est bien mise à jour (elle ne possède plus d’éléments avec des points ou des virgules).

Désormais

nous

allons

pouvoir

gérer

des

phrases

longues

ou

courtes

['Désormais', 'nous', 'allons', 'pouvoir', 'gérer', 'des', 'phrases', 'longues', 'ou', 'courtes']

12° Résolution 2 : On supprime après le split toujours à l’aide de la méthode replace :

phrase = "Désormais, nous allons pouvoir gérer des phrases, longues ou courtes."

mots = phrase.split()


for mot in mots :

    mot = mot.replace(",","")

    mot = mot.replace(".","")

    print(mot)


print(mots)


input("pause")

Là, on constate un problème : la variable mot est modifiée dans la boucle, mais pas mots. C’est normal : mot est défini à partir de mots mais il n’existe pas de lien dans l'autre sens.

Désormais

nous

allons

pouvoir

gérer

des

phrases

longues

ou

courtes

['Désormais,', 'nous', 'allons', 'pouvoir', 'gérer', 'des', 'phrases,', 'longues', 'ou', 'courtes.']

Et ça va me servir à quoi ? Et bien, ça peut être très pratique pour parvenir à décoder une suite d'instructions qu'on donne via un Label. On tape des instructions et l'ordinateur parvient à les effectuer les unes à la suite des autres. Classe non ? Nous en exploiterons tout le potentiel dans la partie sur les animations.

En attendant et en conclusion, choisissez donc avec prudence votre façon de faire méthode 1 ou 2) en fonction de l'influence que vous voulez avoir sur la liste finale.

Nous allons maintenant nous attarder sur les listes. C'est une structure de données très pratique pour stocker et modifier des données.

4 - Les listes

Cette partie va vous permettre d'utiliser la plupart des connaissances vues sur la partie Widget et les deux activités traitant des tests et des boucles.

Nous allons maintenant voir les listes. C'est une structure de données qui va vous permettre de stocker des données créées en grand nombre lors de l'utilisation d'un TANT QUE ou d'une boucle FOR.

Dans l'activité précédente, nous avions créés des Labels colorés mais nous ne pouvions pas stocker leurs références pour pouvoir les modifier par la suite. Nous allons voir comment pallier à cela.

Le plan d'invervention :

  1. Première description des listes
  2. Les listes et les boucles FOR
  3. Liste et interface Tk
  4. Modifier des widgets créés dans une boucle numérique
  5. Modifier des widgets créés dans une boucle nominative

4.1 Première description des listes

Nous avons déjà vu plusieurs exemples de listes plus haut :

  • Le résultat d'un split :
  • ['Désormais', 'nous', 'allons', 'pouvoir', 'gérer', 'des', 'phrases', 'longues', 'ou', 'courtes']

  • L'exemple de la lecture avec un FOR nominatif :
  • maListe = [1,2,9,8,"ah ah ah",45,(45,45,45)]

    for element in maListe :

        print(element)

    input("pause")

Qu'est qu'une liste ? C'est une structure de données :

Le string est donc une structure de données proche de la liste, mais le string n'est pas modifiable après création contrairement à la liste. Vous allez voir qu'on retrouve beaucoup de choses vues avec les strings d'ailleurs.

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 ].

ma_liste = [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.

Ouvrez la console Python et tapez les instructions suivantes pour voir comment cela fonctionne :

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

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

>>> print(ma_liste)

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

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

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

>>> len(ma_liste)

5

Par contre, attention : le premier élément a un index de 0 et le dernier un index de 4.

Si on veut accéder à l'un des éléments d'une liste (le 3e dans l'exemple : 45.2), on cherchera l'index 2 et on tapera ma_liste[2]. Pourquoi ? Simplement car le premier élément 1 est l'élément 0.

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

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

>>> print(ma_liste[2])

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 :

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

>>> print(ma_liste[1:4])

['a',45.2,'bonjour' ]

4.2 Les listes et les boucles FOR

Pour parcourir une liste, il suffit d'utiliser un for numérique ou nominatif :

for truc in ma_liste :

    print(truc)

for numero in range(len(ma_liste)) :

    print(ma_liste[numero])

Avec ma_liste = [1,'a',45.2,"bonjour",'b'], on obtient alors dans les deux cas :

1

a

45.2

bonjour

b

Je voudrais maintenant rentrer des couleurs dans une liste de façon à pouvoir changer à l'envie les couleurs de mes widgets.

Par exemple :

liste_des_couleurs = [ 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow' ]

13° Créer un programme test indépendant qui lit simplement les éléments de la liste précédente avec un for numérique.

4.3 Liste et interface Tk

Nous allons maintenant utiliser ceci sur notre programme d'interface de carrés colorés.

Les 16 carrés en tri de couleur

Reprenons le code de l'activité précédente :

#!/usr/bin/env python

# -*-coding:Utf-8 -*

from tkinter import *

from PIL import Image as Img

from PIL import ImageTk


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

# Définition des variables globales

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

nbrCarres = 16

largeur = 100

marge = 20


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

# 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", (largeur,largeur), (0,0,255))

carreBleuTk = ImageTk.PhotoImage(carreBleu)


carreRouge = Img.new("RGB", (largeur,largeur), (255,0,0))

carreRougeTk = ImageTk.PhotoImage(carreRouge)


carreJaune = Img.new("RGB", (largeur,largeur), (220,220,0))

carreJauneTk = ImageTk.PhotoImage(carreJaune)


for i in range(nbrCarres) :

    c_horizontale = marge + i * (marge+largeur)

    ligne = c_horizontale//480

    c_verticale = marge + ligne * (marge+largeur)

    c_horizontale = c_horizontale%480


    # - - - - - - - - -

    # C'est à partir d'ici qu'on définit l'image à afficher

    # - - - - - - - - -


    if (i%3==0) :

        choix = carreBleuTk

    elif (i%3==1) :

        choix = carreRougeTk

    else :

        choix = carreJauneTk


    Label(fen_princ, image = choix).place(x = c_horizontale, y = c_verticale)


    # - - - - - - - - -

    # Fin de la zone de choix de l'image

    # - - - - - - - - -


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

# Bouclage de la fenêtre fen_princ

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


fen_princ.mainloop()

14° Modifier le code de l'interface carrés colorés pour choisir l'image en fonction du numéro stocké dans la variable-index i de la boucle for numérique et de la liste ci-dessous :

liste_des_couleurs = [ 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow' ]

...CORRECTION...

#!/usr/bin/env python

# -*-coding:Utf-8 -*

from tkinter import *

from PIL import Image as Img

from PIL import ImageTk


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

# Définition des variables globales

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

nbrCarres = 16

largeur = 100

marge = 20

liste_des_couleurs = [ 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow' ]


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

# 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", (largeur,largeur), (0,0,255))

carreBleuTk = ImageTk.PhotoImage(carreBleu)


carreRouge = Img.new("RGB", (largeur,largeur), (255,0,0))

carreRougeTk = ImageTk.PhotoImage(carreRouge)


carreJaune = Img.new("RGB", (largeur,largeur), (220,220,0))

carreJauneTk = ImageTk.PhotoImage(carreJaune)


for i in range(nbrCarres) :

    c_horizontale = marge + i * (marge+largeur)

    ligne = c_horizontale//480

    c_verticale = marge + ligne * (marge+largeur)

    c_horizontale = c_horizontale%480


    # - - - - - - - - -

    # C'est à partir d'ici qu'on définit l'image à afficher

    # - - - - - - - - -


    if (liste_des_couleurs[i]=='blue') :

        choix = carreBleuTk

    elif (liste_des_couleurs[i]=='red') :

        choix = carreRougeTk

    else :

        choix = carreJauneTk


    Label(fen_princ, image = choix).place(x = c_horizontale, y = c_verticale)


    # - - - - - - - - -

    # Fin de la zone de choix de l'image

    # - - - - - - - - -


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

# Bouclage de la fenêtre fen_princ

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


fen_princ.mainloop()

15° Modifier alors simplement les couleurs stockées dans votre liste pour obtenir ceci :

Les deux carrés rouge de suite

Ou alors ceci :

Contour rouge et intérieur bleu

4.4 Modifier des widgets créés dans une boucle numérique

Nous allons maintenant faire mieux : nous allons stocker les références de nos imageTk dans une liste. Cela nous permettra de modifier la couleur des widgets contenus dans la liste à l'aide d'un bouton.

Néanmoins, pour faire cela, nous avons besoin d'en savoir un peu plus sur les listes : comment rajouter des éléments à la volée et comment détruire le contenu par exemple.

Une fiche regroupant les notions importantes sur les Listes est disponible dans la partie FICHES du site. En voici un extrait :

Rajouter des éléments dans une liste

L'une des possibilités est d'utiliser la méthode append qui rajoute des éléments en fin de liste.

>>> a = [1,2,3]

>>> a

[1, 2, 3]

>>> a.append('4')

>>> a

[1, 2, 3, '4']

>>> b = [7,8,9]

>>> a.append(b)

>>> a

[1, 2, 3, '4', [7, 8, 9]]

Cette méthode append permet donc de considérer les listes comme des piles de livres : on rajoute les éléments au dessus des derniers éléments.

append() rajoute au dessus de la pile

Pas d'affectation ! : on remarque que a est modifiée par la méthode, sans présence du signe =.

Il ne faut pas noter quelque chose comme

a = a.append(b)

Sinon, cela crée une nouvelle liste nommée a qui va écraser l'ancienne a. La référence id va donc changer !

Attention également : avec append, on rajoute l'élément b, on ne rajoute pas les éléments de b si x est itérable. Ici, on a bien rajouté une liste b dans la liste a et pas les élements de b dans a.

Dans l'exemple, lorsqu'on rajoute la liste [7,8,9], on obtient ainsi :

[ 1, 2, 3, '4', [7, 8, 9] ]

et pas

[ 1, 2, 3, '4', 7, 8, 9 ]

La fiche complète est disponible si vous en avez besoin. Sinon, Internet permet de se documenter sans aucune difficulté.

Pour l'instant, nous n'avons pas stocké les références des carrés. Nous ne pouvons donc pas agir dessus à l'aide d'un bouton... C'est un peu ennuyeux.

16° Rajouter une variable globale qui contiendra une liste vide. Par exemple listes_des_carres = []. Rajouter ensuite les instructions permettant de réaliser ceci dans votre boucle FOR :

...CORRECTION...

Il suffit de rajouter une ligne dans les définitions et trois lignes dans le FOR :


# ... ... ...

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

# Définition des variables globales

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


liste_des_carres = []


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

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

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


for i in range(nbrCarres) :

    c_horizontale = marge + i * (marge+largeur)

    ligne = c_horizontale//480

    c_verticale = marge + ligne * (marge+largeur)

    c_horizontale = c_horizontale%480

    if (liste_des_couleurs[i]=='blue') :

        choix = carreBleuTk

    elif (liste_des_couleurs[i]=='red') :

        choix = carreRougeTk

    else :

        choix = carreJauneTk

    ref_carre = Label(fen_princ, image = choix)

    ref_carre.place(x = c_horizontale, y = c_verticale)

    liste_des_carres.append(ref_carre)


...CODE COMPLET...

#!/usr/bin/env python

# -*-coding:Utf-8 -*

from tkinter import *

from PIL import Image as Img

from PIL import ImageTk


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

# Définition des variables globales

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

nbrCarres = 16

largeur = 100

marge = 20

liste_des_couleurs = [ 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow' ]

liste_des_carres = []


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

# 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", (largeur,largeur), (0,0,255))

carreBleuTk = ImageTk.PhotoImage(carreBleu)


carreRouge = Img.new("RGB", (largeur,largeur), (255,0,0))

carreRougeTk = ImageTk.PhotoImage(carreRouge)


carreJaune = Img.new("RGB", (largeur,largeur), (220,220,0))

carreJauneTk = ImageTk.PhotoImage(carreJaune)


for i in range(nbrCarres) :

    c_horizontale = marge + i * (marge+largeur)

    ligne = c_horizontale//480

    c_verticale = marge + ligne * (marge+largeur)

    c_horizontale = c_horizontale%480


    # - - - - - - - - -

    # C'est à partir d'ici qu'on définit l'image à afficher

    # - - - - - - - - -


    if (liste_des_couleurs[i]=='blue') :

        choix = carreBleuTk

    elif (liste_des_couleurs[i]=='red') :

        choix = carreRougeTk

    else :

        choix = carreJauneTk


    ref_carre = Label(fen_princ, image = choix)

    ref_carre.place(x = c_horizontale, y = c_verticale)

    liste_des_carres.append(ref_carre)


    # - - - - - - - - -

    # Fin de la zone de choix de l'image

    # - - - - - - - - -


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

# Bouclage de la fenêtre fen_princ

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


fen_princ.mainloop()

Attention : ne faites pas ça :

    ref_carre = Label(fen_princ, image = choix).place(x = c_horizontale, y = c_verticale)

17° Pourquoi ne parvient-on pas à stocker la référence ainsi ?

...CORRECTION...

Je vais écrire la ligne de code en supprimant virtuellement les paramètres :

ref_carre = Label().place()

On voit alors beaucoup mieux ce qu'on stocke : on stocke le résultat renvoyé par la méthode place et pas le résultat renvoyé par le constructeur Label.

Aucune erreur ne sera détectée : ref_carre va simplement contenir True si Python parvient bien à placer votre widget. C'est une erreur dans l'algorithme, pas une erreur de codage.

Et pourquoi s'être compliqué ainsi la tâche pour avoir exactement le même résultat qu'avant ? Et bien la différence n'est pas visuelle mais elle est très importante : nous avons maintenant une liste contenant les références de nos carrés. Nous allons donc pouvoir agir sur eux.

Rappel : pour créer un bouton qui modifie l'attribut image d'un Label, il faut utiliser la méthode config appliquée sur le Label voulu :

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

# Déclarations des fonctions utilisées

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


def coloriage2():

    monLabel.config(image = referenceImageTk)


# Création d'un Button lancant la fonction coloriage2

monBouton = Button(fen_princ, text = "Couleurs 2", command = coloriage2)

monBouton.place(x = ???, y = ???)

Dans le code qui se trouve ci-dessous, j'ai

#!/usr/bin/env python

# -*-coding:Utf-8 -*

from tkinter import *

from PIL import Image as Img

from PIL import ImageTk


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

# Définition des variables globales

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

nbrCarres = 16

largeur = 100

marge = 20


liste_des_couleurs = [ 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow' ]


liste_des_couleurs2 = [ 'blue', "yellow", 'red', 'blue', "yellow", 'red', 'blue', "yellow", 'red', 'blue', "yellow", 'red', 'blue', "yellow", 'red', 'blue', "yellow", 'red' ]


liste_des_carres = []


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

# Déclarations des fonctions utilisées

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


def coloriage2():

    # C'est ici qu'il faut agir

    pass


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

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

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


fen_princ = Tk()

fen_princ.geometry("500x550")


# Création des images de base

carreBleu = Img.new("RGB", (largeur,largeur), (0,0,255))

carreBleuTk = ImageTk.PhotoImage(carreBleu)


carreRouge = Img.new("RGB", (largeur,largeur), (255,0,0))

carreRougeTk = ImageTk.PhotoImage(carreRouge)


carreJaune = Img.new("RGB", (largeur,largeur), (220,220,0))

carreJauneTk = ImageTk.PhotoImage(carreJaune)


for i in range(nbrCarres) :

    c_horizontale = marge + i * (marge+largeur)

    ligne = c_horizontale//480

    c_verticale = marge + ligne * (marge+largeur)

    c_horizontale = c_horizontale%480

    if (liste_des_couleurs[i]=='blue') :

        choix = carreBleuTk

    elif (liste_des_couleurs[i]=='red') :

        choix = carreRougeTk

    else :

        choix = carreJauneTk

    ref_carre = Label(fen_princ, image = choix)

    ref_carre.place(x = c_horizontale, y = c_verticale)

    liste_des_carres.append(ref_carre)


# Création d'un Button lancant la fonction coloriage2

monBouton = Button(fen_princ, text = "Couleurs 2", command = coloriage2)

monBouton.place(x = 20, y = 520)


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

# Bouclage de la fenêtre fen_princ

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


fen_princ.mainloop()

18° Il vous reste à modifier la fonction coloriage2 du bouton pour qu'elle parvienne à :

Vous devriez en avoir pour plusieurs minutes de recherche puisque c'est la première fois que vous faites tout ça.

A vous de jouer.

Voici une correction possible :

...CORRECTION...

#!/usr/bin/env python

# -*-coding:Utf-8 -*

from tkinter import *

from PIL import Image as Img

from PIL import ImageTk


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

# Définition des variables globales

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

nbrCarres = 16

largeur = 100

marge = 20


liste_des_couleurs = [ 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow' ]


liste_des_couleurs2 = [ 'blue', "yellow", 'red', 'blue', "yellow", 'red', 'blue', "yellow", 'red', 'blue', "yellow", 'red', 'blue', "yellow", 'red', 'blue', "yellow", 'red' ]


liste_des_carres = []


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

# Déclarations des fonctions utilisées

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


def coloriage2():

    # C'est ici qu'il faut agir

    for i in range(nbrCarres) :


        # Recherche de la couleur dans la bonne liste sur l'index i

        if (liste_des_couleurs2[i]=='blue') :

            choix = carreBleuTk

        elif (liste_des_couleurs2[i]=='red') :

            choix = carreRougeTk

        else :

            choix = carreJauneTk


        # Recherche du widget Label d'index i

        ref_carre = liste_des_carres[i]


        # Modification du widget Label d'index i

        ref_carre.config(image = choix)


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

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

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


fen_princ = Tk()

fen_princ.geometry("500x550")


# Création des images de base

carreBleu = Img.new("RGB", (largeur,largeur), (0,0,255))

carreBleuTk = ImageTk.PhotoImage(carreBleu)


carreRouge = Img.new("RGB", (largeur,largeur), (255,0,0))

carreRougeTk = ImageTk.PhotoImage(carreRouge)


carreJaune = Img.new("RGB", (largeur,largeur), (220,220,0))

carreJauneTk = ImageTk.PhotoImage(carreJaune)


for i in range(nbrCarres) :

    c_horizontale = marge + i * (marge+largeur)

    ligne = c_horizontale//480

    c_verticale = marge + ligne * (marge+largeur)

    c_horizontale = c_horizontale%480

    if (liste_des_couleurs[i]=='blue') :

        choix = carreBleuTk

    elif (liste_des_couleurs[i]=='red') :

        choix = carreRougeTk

    else :

        choix = carreJauneTk

    ref_carre = Label(fen_princ, image = choix)

    ref_carre.place(x = c_horizontale, y = c_verticale)

    liste_des_carres.append(ref_carre)


# Création d'un Button lancant la fonction coloriage2

monBouton = Button(fen_princ, text = "Couleurs 2", command = coloriage2)

monBouton.place(x = 20, y = 520)


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

# Bouclage de la fenêtre fen_princ

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


fen_princ.mainloop()

4.5 Modifier des widgets créés dans une boucle nominative

Nous avons ci-dessus utilisé un FOR numérique car il fallait faire le lien entre deux listes et faire correspondre le numéro d'index 5 d'une liste avec le numéro d'index 5 de l'autre liste.

Tentons maintenant d'utiliser une boucle FOR nominative sur notre liste de référence :

Nous allons chercher à lire les références une à une, modifier l'attribut image en fonction de cette valeur.

Voici à titre d'exemple une fonction qui va lire les références et qui va changer les carrés en bleus.

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

# Déclarations des fonctions utilisées

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


def coloriageBleu():

    # C'est ici qu'il faut agir

    for ref_carre in liste_des_carres :

        # Modification du widget

        ref_carre.config(image = carreBleuTk)

19° Créer trois boutons qui permettront de changer tous les carrés respectivement en bleu, en rouge et en vert. Donnez des noms clairs à vos fonctions et à vos variables.

Ne cherchez pas à faire la dernière option pour l'instant. Il vous manque encore quelques connaissances.

Dernière étape : parvenir à créer le dernier bouton, le bouton cyclique qui modifie la couleur des Labels en fonction de la couleur actuelle :

4.6 Les subtilités de l'attributs image

Pour cela, il va falloir aller lire le contenu de l'attribut image et le comparer aux trois images Tk carreBleuTk, carreRougeTk ou carreJauneTk.

RAPPEL :

Pour modifier un attribut d'un widget, il faut utiliser la méthode config. Par exemple :

ref_carre.config(image = choix)

Et pour lire le contenu d'un attribut, il faut utiliser la méthode cget :

reference_image = ref_carre.cget("image")

Pour vous aider à comprendre la raison d'une complication que nous allons voir, voici une fonction qu'il faudra associer à l'un de vos boutons :

def tester():

    print("Voici le contenu pour Tkinter des 3 images Tk de base :")

    print("Bleu --- Nom : ", carreBleuTk, " et id : ", id(carreBleuTk))

    print("Rouge --- Nom : ", carreRougeTk, " et id : ", id(carreRougeTk))

    print("Jaune --- Nom : ", carreJauneTk, " et id : ", id(carreJauneTk))

    print("Voici le contenu pour Tkinter des attributs image des Labels :")

    for ref_carre in liste_des_carres :

        print("Attribut image d'un Label --- Nom : ", ref_carre.cget("image"), " et id : ", id(ref_carre.cget("image")))

Si on teste avec la configuration de l'image, on obtiendrait le résultat juste en dessous :

configuration utilisée

Voici le contenu pour Tkinter des 3 images Tk de base :

Bleu --- Nom : pyimage1 et id : 8144

Rouge --- Nom : pyimage2 et id : 1272

Jaune --- Nom : pyimage3 et id : 2008

Voici le contenu pour Tkinter des attributs image des Labels :

Attribut image d'un Label --- Nom : pyimage2 et id : 6304

Attribut image d'un Label --- Nom : pyimage1 et id : 6432

Attribut image d'un Label --- Nom : pyimage3 et id : 6368

Attribut image d'un Label --- Nom : pyimage2 et id : 6304

Attribut image d'un Label --- Nom : pyimage1 et id : 6432

Attribut image d'un Label --- Nom : pyimage3 et id : 6368

Attribut image d'un Label --- Nom : pyimage2 et id : 6304

Attribut image d'un Label --- Nom : pyimage1 et id : 6432

...

On peut donc voir qu'on ne peut pas tester l'égalité entre le contenu de l'attribut et l'une des images Tk avec un code simple d'égalité entre carreRougeTk et ref_carre.cget("image"). Pourquoi ? Regardons l'image Tk rouge et le premier carré qui est rouge également :

Les deux contenus possèdent donc le même nom mais pas le même id. Comme il s'agit d'objets, un test d'égalité donnerait un résultat False à cause de la différence d'identifiant.

carreRougeTk == ref_carre.cget("image")


interprété en une comparaison des identifiants :


1272 == 6304


Cela renvoie donc False alors que les deux sont des images de carrés rouge !

Comment faire alors ? Simplement transformer le contenu en str : on compare alors simplement deux strings et non plus réellement deux objets ayant un même nom mais une identité différente.

str(carreRougeTk) == str(ref_carre.cget("image"))

Ceci donnerait True si le carré étudié est bien un carré rouge.

Voici donc la fonction coloriageCyclique() qui permet de transformer uniquement les carrés bleus en carrés rouge pour l'instant :

def coloriageCyclique():

    for ref_carre in liste_des_carres :

        if str(carreBleuTk) == str(ref_carre.cget("image")) :

            ref_carre.config(image = carreRougeTk)

20° Utiliser la fonction en l'associant à un bouton. Compléter ensuite la fonction pour qu'elle puisse transformer les rouges en jaunes et les jaunes en bleus.

Voici une correction possible :

...CORRECTION...

#!/usr/bin/env python

# -*-coding:Utf-8 -*

from tkinter import *

from PIL import Image as Img

from PIL import ImageTk


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

# Définition des variables globales

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

nbrCarres = 16

largeur = 100

marge = 20


liste_des_couleurs = [ 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow', 'red', "blue", 'yellow' ]

liste_des_couleurs2 = [ 'blue', "yellow", 'red', 'blue', "yellow", 'red', 'blue', "yellow", 'red', 'blue', "yellow", 'red', 'blue', "yellow", 'red', 'blue', "yellow", 'red' ]

liste_des_carres = []


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

# Déclaration des fonctions utilisées

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


def coloriage2():

    # C'est ici qu'il faut agir

    for i in range(nbrCarres) :


        # Recherche de la couleur dans la bonne liste sur l'index i

        if (liste_des_couleurs2[i]=='blue') :

            choix = carreBleuTk

        elif (liste_des_couleurs2[i]=='red') :

            choix = carreRougeTk

        else :

            choix = carreJauneTk


        # Recherche du widget Label d'index i

        ref_carre = liste_des_carres[i]


        # Modification du widget Label d'index i

        ref_carre.config(image = choix)


def coloriageBleu():

    for ref_carre in liste_des_carres :

        # Modification du widget

        ref_carre.config(image = carreBleuTk)


def coloriageRouge():

    for ref_carre in liste_des_carres :

        # Modification du widget

        ref_carre.config(image = carreRougeTk)


def coloriageJaune():

    for ref_carre in liste_des_carres :

        # Modification du widget

        ref_carre.config(image = carreJauneTk)


def coloriageCyclique():

    for ref_carre in liste_des_carres :

        if str(carreBleuTk) == str(ref_carre.cget("image")) :

            ref_carre.config(image = carreRougeTk)

        elif str(carreRougeTk) == str(ref_carre.cget("image")) :

            ref_carre.config(image = carreJauneTk)

        else :

            ref_carre.config(image = carreBleuTk)


def tester():

    print("Voici le contenu pour Tkinter des 3 images Tk de base :")

    print("Bleu --- Nom : ", carreBleuTk, " et id : ", id(carreBleuTk))

    print("Rouge --- Nom : ", carreRougeTk, " et id : ", id(carreRougeTk))

    print("Jaune --- Nom : ", carreJauneTk, " et id : ", id(carreJauneTk))

    print("Voici le contenu pour Tkinter des attributs image des Labels :")

    for ref_carre in liste_des_carres :

        print("Attribut image d'un Label --- Nom : ", ref_carre.cget("image"), " et id : ", id(ref_carre.cget("image")))


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

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

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


fen_princ = Tk()

fen_princ.geometry("500x550")


# Création des images de base

carreBleu = Img.new("RGB", (largeur,largeur), (0,0,255))

carreBleuTk = ImageTk.PhotoImage(carreBleu)


carreRouge = Img.new("RGB", (largeur,largeur), (255,0,0))

carreRougeTk = ImageTk.PhotoImage(carreRouge)


carreJaune = Img.new("RGB", (largeur,largeur), (220,220,0))

carreJauneTk = ImageTk.PhotoImage(carreJaune)


for i in range(nbrCarres) :

    c_horizontale = marge + i * (marge+largeur)

    ligne = c_horizontale//480

    c_verticale = marge + ligne * (marge+largeur)

    c_horizontale = c_horizontale%480

    if (liste_des_couleurs[i]=='blue') :

        choix = carreBleuTk

    elif (liste_des_couleurs[i]=='red') :

        choix = carreRougeTk

    else :

        choix = carreJauneTk

    ref_carre = Label(fen_princ, image = choix)

    ref_carre.place(x = c_horizontale, y = c_verticale)

    liste_des_carres.append(ref_carre)


# Création d'un Button lancant la fonction coloriage2

monBouton = Button(fen_princ, text = "Couleurs 2", command = coloriage2)

monBouton.place(x = 20, y = 520)


boutonBleu = Button(fen_princ, text = "En bleu", command = coloriageBleu, bg = "blue", fg = "black")

boutonBleu.place(x = 120, y = 520)


boutonRouge = Button(fen_princ, text = "En rouge", command = coloriageRouge, bg = "red", fg = "black")

boutonRouge.place(x = 220, y = 520)


boutonJaune = Button(fen_princ, text = "En jaune", command = coloriageJaune, bg = "yellow", fg = "black")

boutonJaune.place(x = 320, y = 520)


boutonCycle = Button(fen_princ, text = "Cyclique", command = coloriageCyclique, bg = "black", fg = "yellow")

boutonCycle.place(x = 420, y = 520)


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

# Bouclage de la fenêtre fen_princ

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


fen_princ.mainloop()

Remarque : Ca parait compliqué. Nous aurions pu faire bien mieux en utilisant la notion d'objet. Nous aurions ainsi pu stocker directement la vraie référence de l'image dans notre objet. Vous verrez cela plus tard avec la programmation objet.