• Aucun résultat trouvé

Intégration de widgets composites dans une application synthèse

10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45

self.controle = [0]*3 # liste des panneaux de contrôle # Instanciation du canevas avec axes X et Y :

self.gra = OscilloGraphe(self, larg =400, haut=200)

self.gra.configure(bg ='white', bd=2, relief=SOLID)

self.gra.pack(side =TOP, pady=5)

# Instanciation de 3 panneaux de contrôle (curseurs) : for i in range(3):

self.controle[i] = ChoixVibra(self, self.couleur[i])

self.controle[i].pack()

# Désignation de l'événement qui déclenche l'affichage des tracés : self.master.bind('<Control-Z>', self.montreCourbes)

self.master.title('Mouvements vibratoires harmoniques')

self.pack()

def montreCourbes(self, event):

"""(Ré)Affichage des trois graphiques élongation/temps""" for i in range(3):

# D'abord, effacer le tracé précédent (éventuel) : self.gra.delete(self.trace[i])

# Ensuite, dessiner le nouveau tracé : if self.controle[i].chk.get():

self.trace[i] = self.gra.traceCourbe( coul =self.couleur[i], freq =self.controle[i].freq, phase = self.controle[i].phase,

ampl =self.controle[i].ampl)

#### Code pour tester la classe : ###

if __name__ == '__main__': ShowVibra().mainloop()

Lignes 1-2 : Nous pouvons nous passer d'importer le module Tkinter : chacun de ces deux modules s'en charge déjà. Ligne 4 : Puisque nous commençons à connaître les bonnes techniques, nous décidons de construire l'application elle- même sous la forme d'une classe, dérivée de la classe Frame() : ainsi nous pourrons plus tard l'intégrer toute entière dans d'autres projets, si le cœur nous en dit.

Lignes 8-10 : Définition de quelques variables d'instance (3 listes) : les trois courbes tracées seront des objets graphiques, dont les couleurs sont pré-définies dans la liste self.couleur ; nous devons préparer également une liste self.trace pour mémoriser les références de ces objets graphiques, et enfin une liste self.controle pour mémoriser les références des trois panneaux de contrôle.

Lignes 13 à 15 : Instanciation du widget d'affichage. Étant donné que la classe OscilloGraphe() a été obtenue par dérivation de la classe Canvas(), il est toujours possible de configurer ce widget en redéfinissant les options spécifiques de cette classe (ligne 13).

Lignes 18 à 20 : Pour instancier les trois widgets « panneau de contrôle », on utilise une boucle. Leurs références sont mémorisées dans la liste self.controle préparée à la ligne 10. Ces panneaux de contrôle sont instanciés comme esclaves du présent widget, par l'intermédiaire du paramètre self. Un second paramètre leur transmet la couleur du tracé à contrôler.

Lignes 23-24 : Au moment de son instanciation, chaque widget Tkinter reçoit automatiquement un attribut master qui contient la référence de la fenêtre principale de l'application. Cet attribut se révèle particulièrement utile si la fenêtre principale a été instanciée implicitement par Tkinter, comme c'est le cas ici. Rappelons en effet que lorsque nous démarrons une application en instanciant directement un widget tel que Frame, par exemple (c'est ce que nous avons fait à la ligne 4), Tkinter instancie automatiquement une fenêtre maîtresse pour ce widget (un objet de la classe Tk()). Comme cet objet a été créé automatiquement, nous ne disposons d'aucune référence dans notre code pour y accéder, si ce n'est par l'intermédiaire de l'attribut master que Tkinter associe automatiquement à chaque widget. Nous nous servons de cette référence pour redéfinir le bandeau-titre de la fenêtre principale (à la ligne 24), et pour y attacher un gestionnaire d'événement (à la ligne 23).

Lignes 27 à 40 : La méthode décrite ici est le gestionnaire des événements <Maj-Ctrl-Z> spécifiquement déclenchés par nos widgets ChoixVibra() (ou « panneaux de contrôle »), chaque fois que l'utilisateur exerce une action sur un curseur ou une case à cocher. Dans tous les cas, les graphiques éventuellement présents sont d'abord effacés (ligne 28) à l'aide de la méthode delete() : le widget OscilloGraphe() a hérité cette méthode de sa classe parente Canvas(). Ensuite, de nouvelles courbes sont retracées, pour chacun des panneaux de contrôle dont on a coché la case « Afficher ». Chacun des objets ainsi dessinés dans le canevas possède un numéro de référence, renvoyé par la méthode traceCourbe() de notre widget OscilloGraphe(). Les numéros de référence de nos dessins sont mémorisés dans la liste self.trace. Ils permettent d'effacer individuellement chacun d'entre eux (cfr. instruction de la ligne 28).

Commentaires

Lignes 38-40 : Les valeurs de fréquence, phase & amplitude que l'on transmet à la méthode traceCourbe() sont les attributs d'instance correspondants de chacun des trois panneaux de contrôle, eux-mêmes mémorisés dans la liste self.controle. Nous pouvons récupérer ces attributs en utilisant la qualification des noms par points.

Exercices

Modifiez le script, de manière à obtenir l'aspect ci-dessous (écran d'affichage avec grille de référence, panneaux de contrôle entourés d'un sillon) :

1.

Modifiez le script, de manière à faire apparaître et contrôler 4 graphiques au lieu de trois. Pour la couleur du quatrième graphique, choisissez par exemple : 'blue', 'navy', 'maroon', ...

2.

Aux lignes 33-35, nous récupérons les valeurs des fréquence, phase & amplitude choisies par l'utilisateur sur chacun des trois panneaux de contrôle, en accédant directement aux attributs d'instance correspondants. Python autorise ce raccourci - et c'est bien pratique – mais cette technique est dangereuse. Elle enfreint l'une des recommandations de la théorie générale de la « programmation orientée objet », qui préconise que l'accès aux propriétés des objets soit toujours pris en charge par des méthodes spécifiques. Pour respecter cette recommandation, ajoutez à la classe ChoixVibra() une méthode supplémentaire que vous appellerez valeurs(), et qui renverra un tuple contenant les valeurs de la fréquence, la phase et l'amplitude choisies. Les lignes 33 à 35 du présent script pourront alors être remplacées par quelque chose comme :

freq, phase, ampl = self.control[i].valeurs()</li> 3.

Écrivez une petite application qui fait apparaître une fenêtre avec un canevas et un widget curseur (Scale). Dans le canevas, dessinez un cercle, dont l'utilisateur pourra faire varier la taille à l'aide du curseur.

4.

Écrivez un script qui créera deux classes : une classe « Application », dérivée de Frame(), dont le constructeur instanciera un canevas de 400x400 pixels, ainsi que deux boutons. Dans le canevas, vous instancierez un objet de la classe « Visage » décrite ci-après. La classe « Visage » servira à définir des objets graphiques censés représenter des visages humains simplifiés. Ces visages seront constitués d'un cercle principal dans lequel trois ovales plus petits représenteront deux yeux et une bouche (ouverte). Une méthode "fermer" permettra de remplacer l'ovale de la bouche par une ligne horizontale. Une méthode « ouvrir » permettra de restituer la bouche de forme ovale. Les deux boutons définis dans la classe « Application » serviront respectivement à fermer et à ouvrir la bouche de l'objet « Visage » installé dans le canevas.

5.

Exercice de synthèse : élaboration d'un dictionnaire de couleurs. But : réaliser un petit programme utilitaire, qui puisse

vous aider à construire facilement et rapidement un nouveau dictionnaire de couleurs, lequel permettrait l'accès technique à une couleur quelconque par l'intermédiaire de son nom usuel en français. Contexte : En manipulant divers objets colorés avec Tkinter, vous avez constaté que cette bibliothèque graphique accepte qu'on lui désigne les couleurs les plus fondamentales sous la forme de chaînes de caractères contenant leur nom en anglais : 'red', 'blue', etc. Vous savez cependant qu'un ordinateur ne peut traiter que des informations numérisées. Cela implique que la désignation d'une couleur quelconque doit nécessairement tôt ou tard être encodée sous la forme d'un nombre. Il faut bien entendu adopter pour cela une convention, et celle-ci peut varier d'un système à un autre. L'une de ces conventions, parmi les plus courantes, consiste à représenter une couleur à l'aide de trois octets, qui indiqueront 6.

respectivement les intensités des trois composantes rouge, verte et bleue de cette couleur. Cette convention peut être utilisée avec Tkinter pour accéder à n'importe quelle nuance colorée. Vous pouvez en effet lui indiquer la couleur d'un élément graphique quelconque, à l'aide d'une chaîne de 7 caractères telle que '#00FA4E'. Dans cette chaîne, le premier caractère (#) signifie que ce qui suit est une valeur hexadécimale. Les six caractères suivants représentent les 3 valeurs hexadécimales des 3 composantes R, V et B. Pour visualiser concrètement la correspondance entre une couleur quelconque et son code, vous pouvez essayer le petit programme utilitaire tkColorChooser.py (qui se trouve généralement dans le sous-répertoire /lib-tk de votre installation de Python). Étant donné qu'il n'est pas facile pour les humains que nous sommes de mémoriser de tels codes hexadécimaux, Tkinter est également doté d'un dictionnaire de conversion, qui autorise l'utilisation de noms communs pour un certain nombre de couleurs parmi les plus courantes, mais cela ne marche que pour des noms de couleurs exprimés en anglais. Le but du présent exercice est de réaliser un logiciel qui facilitera la construction d'un dictionnaire équivalent en français, lequel pourrait ensuite être incorporé à l'un ou l'autre de vos propres programmes. Une fois construit, ce dictionnaire serait donc de la forme : {'vert':'#00FF00', 'bleu':'#0000FF', ... etc ...}.

Cahier des charges 

L'application à réaliser sera une application graphique, construite autour d'une classe. Elle comportera une fenêtre avec un certain nombre de champs d'entrée et de boutons, afin que l'utilisateur puisse aisément encoder de nouvelles couleurs en indiquant à chaque fois son nom français dans un champ, et son code hexadécimal dans un autre. Lorsque le dictionnaire contiendra déjà un certain nombre de données, il devra être possible de le tester, c'est-à-dire d'entrer un nom de couleur en français et de retrouver le code hexadécimal correspondant à l'aide d'un bouton (avec affichage éventuel d'une zone colorée). Un bouton provoquera l'enregistrement du dictionnaire dans un fichier texte. Un autre permettra de reconstruire le dictionnaire à partir du fichier.

Le script ci-dessous correspond à une ébauche de projet dessinant des ensembles de dés à jouer disposés à l'écran de plusieurs manières différentes (cette ébauche pourrait être une première étape dans la réalisation d'un logiciel de jeu). L'exercice consistera à analyser ce script et à le compléter. Vous vous placerez ainsi dans la situation d'un

programmeur chargé de continuer le travail commencé par quelqu'un d'autre, ou encore dans celle de l'informaticien prié de participer à un travail d'équipe. Commencez par analyser ce script, et ajoutez-y des commentaires, en particulier aux lignes marquées : #*** , afin de montrer que vous comprenez ce que doit faire le programme à ces emplacements :

from Tkinter import * class FaceDom:

def __init__(self, can, val, pos, taille =70): self.can =can

# ***

x, y, c = pos[0], pos[1], taille/2

can.create_rectangle(x -c, y-c, x+c, y+c, fill ='ivory', width =2) d = taille/3 # *** self.pList =[] # *** pDispo = [((0,0),), ((-d,d),(d,-d)), ((-d,-d), (0,0), (d,d))] disp = pDispo[val -1] # *** for p in disp: self.cercle(x +p[0], y +p[1], 5, 'red')

def cercle(self, x, y, r, coul): # ***

self.pList.append(self.can.create_oval(x-r, y-r, x+r, y+r, fill=coul))

def effacer(self): # ***

for p in self.pList: self.can.delete(p)

class Projet(Frame):

def __init__(self, larg, haut): Frame.__init__(self)

self.larg, self.haut = larg, haut

self.can = Canvas(self, bg='dark green', width =larg, height =haut) self.can.pack(padx =5, pady =5)

# ***

bList = [("A", self.boutA), ("B", self.boutB), ("C", self.boutC), ("D", self.boutD), ("Quitter", self.boutQuit)]

for b in bList:

Button(self, text =b[0], command =b[1]).pack(side =LEFT) self.pack()

7.

def boutA(self):

self.d3 = FaceDom(self.can, 3, (100,100), 50)

def boutB(self):

self.d2 = FaceDom(self.can, 2, (200,100), 80)

def boutC(self):

self.d1 = FaceDom(self.can, 1, (350,100), 110)

def boutD(self): # ***

self.d3.effacer() def boutQuit(self):

self.master.destroy()

Projet(500, 300).mainloop()

Modifiez ensuite ce script, afin qu'il corresponde au cahier des charges suivant : Le canevas devra être plus grand : 600 x 600 pixels.

Les boutons de commande devront être déplacés à droite et espacés davantage.

La taille des points sur une face de dé devra varier proportionnellement à la taille de cette face

Variante 1 : Ne conservez que les 2 boutons A et B. Chaque utilisation du bouton A fera apparaître 3 nouveaux dés (de même taille, plutôt petits) disposés sur une colonne (verticale), les valeurs de ces dés étant tirées au hasard entre 1 et 6. Chaque nouvelle colonne sera disposée à la droite de la précédente. Si l'un des tirages de 3 dés correspond à 4, 2, 1 (dans n'importe quel ordre), un message « gagné » sera affiché dans la fenêtre (ou dans le canevas). Le bouton B provoquera l'effacement complet (pas seulement les points !) de tous les dés affichés.

Variante 2 : Ne conservez que les 2 boutons A et B. Le bouton A fera apparaître 5 dés disposés en quinconce (c.à.d. comme les points d'une face de valeur 5). Les valeurs de ces dés seront tirées au hasard entre 1 et 6, mais il ne pourra pas y avoir de doublons. Le bouton B provoquera l'effacement complet (pas seulement les points !) de tous les dés affichés.

Variante 3 : Ne conservez que les 3 boutons A, B et C. Le bouton A fera apparaître 13 dés de même taille disposés en cercle. Chaque utilisation du bouton B provoquera un changement de valeur du premier dé, puis du deuxième, du troisième, etc. La nouvelle valeur d'un dé sera à chaque fois égale a sa valeur précédente augmentée d'une unité, sauf dans le cas ou la valeur précédente était 6 : dans ce cas la nouvelle valeur est 1, et ainsi de suite. Le bouton C provoquera l'effacement complet (pas seulement les points !) de tous les dés affichés.

Variante 4 : Ne conservez que les 3 boutons A, B et C. Le bouton A fera apparaître 12 dés de même taille disposés sur deux lignes de 6. Les valeurs des dés de la première ligne seront dans l'ordre 1, 2, 3, 4, 5, 6. Les valeurs des dés de la seconde ligne seront tirées au hasard entre 1 et 6. Chaque utilisation du bouton B provoquera un changement de valeur aléatoire du premier dé de la seconde ligne, tant que cette valeur restera différente de celle du dé

correspondant dans la première ligne. Lorsque le 1er dé de la 2e ligne aura acquis la valeur de son correspondant, c'est la valeur du 2e dé de la seconde ligne qui sera changée au hasard, et ainsi de suite, jusqu'à ce que les 6 faces du bas soient identiques à celles du haut. Le bouton C provoquera l'effacement complet (pas seulement les points !) de tous les dés affichés.

Solution Réfléchissez ! 1. Réfléchissez ! 2. Réfléchissez ! 3. Réfléchissez ! 4. Réfléchissez ! 5.

# Dictionnaire de couleurs from Tkinter import *

# Module donnant accès aux boîtes de dialogue standard pour # la recherche de fichiers sur disque :

from tkFileDialog import asksaveasfile, askopenfile class Application(Frame):

'''Fenêtre d'application''' def __init__(self):

Frame.__init__(self)

self.master.title("Création d'un dictionnaire de couleurs") self.dico ={} # création du dictionnaire

# Les widgets sont regroupés dans deux cadres (Frames) : frSup =Frame(self) # cadre supérieur contenant 6 widgets Label(frSup, text ="Nom de la couleur :",

width =20).grid(row =1, column =1)

self.enNom =Entry(frSup, width =25) # champ d'entrée pour self.enNom.grid(row =1, column =2) # le nom de la couleur Button(frSup, text ="Existe déjà ?", width =12,

command =self.chercheCoul).grid(row =1, column =3) Label(frSup, text ="Code hexa. corresp. :",

width =20).grid(row =2, column =1)

self.enCode =Entry(frSup, width =25) # champ d'entrée pour self.enCode.grid(row =2, column =2) # le code hexa. Button(frSup, text ="Test", width =12,

command =self.testeCoul).grid(row =2, column =3) frSup.pack(padx =5, pady =5)

frInf =Frame(self) # cadre inférieur contenant le reste self.test = Label(frInf, bg ="white", width =45, # zone de test

height =7, relief = SUNKEN) self.test.pack(pady =5)

Button(frInf, text ="Ajouter la couleur au dictionnaire", command =self.ajouteCoul).pack()

Button(frInf, text ="Enregistrer le dictionnaire", width =25, command =self.enregistre).pack(side = LEFT, pady =5) Button(frInf, text ="Restaurer le dictionnaire", width =25,

command =self.restaure).pack(side =RIGHT, pady =5) frInf.pack(padx =5, pady =5)

self.pack()

def ajouteCoul(self):

"ajouter la couleur présente au dictionnaire"

if self.testeCoul() ==0: # une couleur a-t-elle été définie ? return

nom = self.enNom.get()

if len(nom) >1: # refuser les noms trop petits self.dico[nom] =self.cHexa

else: 6.

self.test.config(text ="%s : nom incorrect" % nom, bg ='white') def chercheCoul(self):

"rechercher une couleur déjà inscrite au dictionnaire" nom = self.enNom.get()

if self.dico.has_key(nom):

self.test.config(bg =self.dico[nom], text ="") else:

self.test.config(text ="%s : couleur inconnue" % nom, bg ='white')

def testeCoul(self):

"vérifier la validité d'un code hexa. - afficher la couleur corresp." try:

self.cHexa =self.enCode.get()

self.test.config(bg =self.cHexa, text ="") return 1

except:

self.test.config(text ="Codage de couleur incorrect", bg ='white') return 0

def enregistre(self):

"enregistrer le dictionnaire dans un fichier texte"

# Cette méthode utilise une boîte de dialogue standard pour la # sélection d'un fichier sur disque. Tkinter fournit toute une série # de fonctions associées à ces boîtes, dans le module tkFileDialog. # La fonction ci-dessous renvoie un objet-fichier ouvert en écriture : ofi =asksaveasfile(filetypes=[("Texte",".txt"),("Tous","*")])

for clef, valeur in self.dico.items(): ofi.write("%s %s\n" % (clef, valeur)) ofi.close()

def restaure(self):

"restaurer le dictionnaire à partir d'un fichier de mémorisation" # La fonction ci-dessous renvoie un objet-fichier ouvert en lecture : ofi =askopenfile(filetypes=[("Texte",".txt"),("Tous","*")])

lignes = ofi.readlines() for li in lignes:

cv = li.split() # extraction de la clé et la valeur corresp. self.dico[cv[0]] = cv[1]

ofi.close() if __name__ == '__main__':

Application().mainloop() (variante 3) :

from Tkinter import * from random import randrange from math import sin, cos, pi class FaceDom:

def __init__(self, can, val, pos, taille =70): self.can =can

x, y, c = pos[0], pos[1], taille/2

self. carre = can.create_rectangle(x -c, y-c, x+c, y+c, fill ='ivory', width =2) d = taille/3

# disposition des points sur la face, pour chacun des 6 cas : self.pDispo = [((0,0),), ((-d,d),(d,-d)), ((-d,-d), (0,0), (d,d)), ((-d,-d),(-d,d),(d,-d),(d,d)), ((-d,-d),(-d,d),(d,-d),(d,d),(0,0)), ((-d,-d),(-d,d),(d,-d),(d,d),(d,0),(-d,0))]

self.x, self.y, self.dim = x, y, taille/15

self.pList =[] # liste contenant les points de cette face self.tracer_points(val)

def tracer_points(self, val): 7.

# créer les dessins de points correspondant à la valeur val : disp = self.pDispo[val -1]

for p in disp:

self.cercle(self.x +p[0], self.y +p[1], self.dim, 'red') self.val = val

def cercle(self, x, y, r, coul):

self.pList.append(self.can.create_oval(x-r, y-r, x+r, y+r, fill=coul))

def effacer(self, flag =0): for p in self.pList:

self.can.delete(p) if flag:

self.can.delete(self.carre)

class Projet(Frame):

def __init__(self, larg, haut): Frame.__init__(self)

self.larg, self.haut = larg, haut

self.can = Canvas(self, bg='dark green', width =larg, height =haut) self.can.pack(padx =5, pady =5)

# liste des boutons à installer, avec leur gestionnaire : bList = [("A", self.boutA), ("B", self.boutB),

("C", self.boutC), ("Quitter", self.boutQuit)] bList.reverse() # inverser l'ordre de la liste for b in bList:

Button(self, text =b[0], command =b[1]).pack(side =RIGHT, padx=3) self.pack()

self.des =[] # liste qui contiendra les faces de dés self.actu =0 # réf. du dé actuellement sélectionné

def boutA(self): if len(self.des):

return # car les dessins existent déjà ! a, da = 0, 2*pi/13

for i in range(13):

cx, cy = self.larg/2, self.haut/2

x = cx + cx*0.75*sin(a) # pour disposer en cercle,

y = cy + cy*0.75*cos(a) # on utilise la trigono !

self.des.append(FaceDom(self.can, randrange(1,7) , (x,y), 65)) a += da

def boutB(self):

# incrémenter la valeur du dé sélectionné. Passer au suivant : v = self.des[self.actu].val v = v % 6 v += 1 self.des[self.actu].effacer() self.des[self.actu].tracer_points(v) self.actu += 1 self.actu = self.actu % 13 def boutC(self):

for i in range(len(self.des)): self.des[i].effacer(1) self.des =[]

self.actu =0

def boutQuit(self): self.master.destroy()

Projet(600, 600).mainloop()

Comme nous l'avons déjà signalé précédemment, Python vous permet d'accéder aux attributs d'instance en utilisant la qualification des noms par points. D'autres langages de programmation l'interdisent, ou bien ne l'autorisent que moyennant une déclaration particulière de ces attributs (distinction entre attributs privés et publics). Sachez en tous cas que ce n'est pas recommandé : le bon usage de la programmation orientée objet stipule en effet que vous ne devez 1.

pouvoir accéder aux attributs des objets que par l'intermédiaire de méthodes spécifiques.

Tkinter autorise également de construire la fenêtre principale d'une application par dérivation d'une classe de widget (le

plus souvent, il s'agira d'un widget Frame()). La fenêtre englobant ce widget sera automatiquement ajoutée. 2.

En fait, on devrait plutôt appeler cela un message (qui est lui-même la notification d'un événement). Programmes pilotés par des événements.

3.

Il va de soi que nous pourrions rassembler toutes les classes que nous construisons dans un seul module. 4.

Fichiers

Jusqu'à présent, les programmes que nous avons réalisés ne traitaient qu'un très petit nombre de données. Nous pouvions donc à chaque fois inclure ces données dans le corps du programme lui-même (par exemple dans une liste). Cette façon de procéder devient cependant tout à fait inadéquate lorsque l'on souhaite traiter une quantité d'information plus importante.

Imaginons par exemple que nous voulons écrire un petit programme exerciseur qui fasse apparaître à l'écran des questions à choix multiple, avec traitement automatique des réponses de l'utilisateur. Comment allons-nous mémoriser le texte des questions elles-mêmes ?

L'idée la plus simple consiste à placer chacun de ces textes dans une variable, en début de programme, avec des instructions d'affectation du genre : a = "Quelle est la capitale du Guatémala ?"

b = "Qui a succédé à Henri IV ?"

c = "Combien font 26 × 43 ?"

etc.

Cette idée est malheureusement beaucoup trop simpliste. Tout va se compliquer en effet lorsque nous essayerons d'élaborer la suite du programme, c'est- à-dire les instructions qui devront servir à sélectionner au hasard l'une ou l'autre de ces questions pour les présenter à l'utilisateur. Employer par exemple une longue suite d'instructions if ... elif ... elif ... comme dans l'exemple ci-dessous n'est certainement pas la bonne solution (ce serait d'ailleurs bien pénible à écrire : n'oubliez pas que nous souhaitons traiter un grand nombre de questions !) :

if choix == 1: selection = a elifchoix == 2: selection = b elifchoix == 3: selection = c ... etc.

La situation se présente déjà beaucoup mieux si nous faisons appel à une liste : liste = ["Qui a vaincu Napoléon à Waterloo ?",

"Comment traduit-on 'informatique' en anglais ?",

"Quelle est la formule chimique du méthane ?", ... etc ...] On peut en effet extraire n'importe quel élément de cette liste à l'aide de son indice. Exemple :

printliste[2] ===> "Quelle est la formule chimique du méthane ?" (rappel : l'indiçage commence à partir de zéro)

Même si cette façon de procéder est déjà nettement meilleure que la précédente, nous sommes toujours confrontés à plusieurs problèmes gênants :