Comportement d'une boucle sur un tableau modifié dans la boucle

Comportement d'une boucle sur un tableau modifié dans la boucle - Python - Programmation

Marsh Posté le 15-10-2007 à 21:36:57    

Salut à tous,
Je continue ma découverte de Python et évidemment chaque jour j'en découvre un peu plus.
Là, j'ai été confronté à un problème qui m'a bloqué pendant un quart d'heure avant que je ne comprenne d'où venait le bug. En fait, j'avais instancié une boucle sur un tableau pour en éliminer certains éléments et ma boucle n'éliminait pas les bons
Ex:

Code :
  1. tab=['a', 'z', 'e', 'r', 't']
  2.  
  3. for i, c in enumerate(tab):
  4.    print i, c, tab
  5.    if (i % 2) == 0:
  6.        tab.remove(c)
  7.  
  8. # Résultat
  9. # 0 a ['a', 'z', 'e', 'r', 't']    => Normal, on est à la première itération => c vaut 'a' et le 'a' sera supprimé
  10. # 1 e ['z', 'e', 'r', 't']         => Bizarre, on est à la seconde itération, c devrait valoir 'z' mais il vaut 'e'
  11. # 2 r ['z', 'e', 'r', 't']         => Bizarre, on est à la troisième itération, c devrait valoir 'e' mais il vaut 'r'
  12. # Et en plus il ne traite pas le 't'
  13.  
  14. print tab
  15. # ['z', 'e', 't']


Il semble qu'à chaque itération de la boucle, le tableau est réévalué dans son intégralité. Donc à la seconde itération c vaut le 2° élément du tableau actuel soit 'e'. Et à la 3°, il vaut le 3° élément soit 'r'. Et comme cette lettre est supprimée, le nombre d'éléments passe à 3 et comme j'ai atteint la 3° itération Python considère que j'ai traité tout mon tableau. C'est ça ???


Message édité par Sve@r le 15-10-2007 à 21:41:16

---------------
Vous ne pouvez pas apporter la prospérité au pauvre en la retirant au riche.
Reply

Marsh Posté le 15-10-2007 à 21:36:57   

Reply

Marsh Posté le 15-10-2007 à 21:49:02    

On modifie pas un tableau quand on itère dessus via un iterator en python.

 

Jamais.

 

Si tu veux filtrer, tu utilises soit filter soir une list comprehension soit une génération manuelle de liste de sortie (à base de for et de list.append):

 
Code :
  1. >>> tab=['a', 'z', 'e', 'r', 't']
  2. >>> out = [c for i, c in enumerate(tab) if i%2 != 0]
  3. >>> out
  4. ['z', 'r']
  5. >>>


Citation :

Il semble qu'à chaque itération de la boucle, le tableau est réévalué dans son intégralité. Donc à la seconde itération c vaut le 2° élément du tableau actuel soit 'e'. Et à la 3°, il vaut le 3° élément soit 'r'. Et comme cette lettre est supprimée, le nombre d'éléments passe à 3 et comme j'ai atteint la 3° itération Python considère que j'ai traité tout mon tableau. C'est ça ???


En gros, Python génère un iterator qui garde simplement un index sur l'élément courant du tableau, sans rien faire d'autre.

 

En décomposé, si on appelle cet index caché __index__:

 
Code :
  1. for c, i in enumerate(tab): # __index__ = 0, tab = ['a', 'z', 'e', 'r', 't'], c = 'a'
  2.    if (i % 2) == 0: # true (i == 0)
  3.        tab.remove(c) # tab = ['z', 'e', 'r', 't']
  4. for c, i in enumerate(tab): # __index__ = 1, tab = ['z', 'e', 'r', 't'], c = 'e'
  5.    if (i % 2) == 0: # false (i == 1)
  6.        pass
  7. for i, c in enumerate(tab): # __index__ = 2, tab = ['z','e','r','t'], c = 'r'
  8.   if (i % 2) == 0: # true (i == 2)
  9.       tab.remove(c) # tab = ['z','e','t']
  10. for i, c in enumerate(tab): # __index__ = 3, tab = ['z','e','t'], fin de l'itération
  11.   pass
 

   

Message cité 1 fois
Message édité par masklinn le 15-10-2007 à 21:56:25

---------------
Stick a parrot in a Call of Duty lobby, and you're gonna get a racist parrot. — Cody
Reply

Marsh Posté le 15-10-2007 à 22:39:43    

masklinn a écrit :

Si tu veux filtrer, tu utilises soit filter soir une list comprehension soit une génération manuelle de liste de sortie (à base de for et de list.append):
 

Code :
  1. >>> tab=['a', 'z', 'e', 'r', 't']
  2. >>> out = [c for i, c in enumerate(tab) if i%2 != 0]
  3. >>> out
  4. ['z', 'r']
  5. >>>



Oui, j'ai effectivement pensé à créer une 2° liste contenant mes éléments à supprimer puis en itérant cette liste et en appelant tab.remove(). Mais quelque part ça me gêne de devoir dupliquer mon tableau pour le remover. Ca peut pas marcher si je crée un indice perso qui n'évolue que quand il faut style

Code :
  1. i=0
  2. lg=len(tab)
  3. for j in range(lg):
  4.    if tab[i] != ce que je veux:
  5.        del tab[i]
  6.    else
  7.        i+=1


???


---------------
Vous ne pouvez pas apporter la prospérité au pauvre en la retirant au riche.
Reply

Marsh Posté le 15-10-2007 à 22:46:03    

Sve@r a écrit :


Oui, j'ai effectivement pensé à créer une 2° liste contenant mes éléments à supprimer puis en itérant cette liste et en appelant tab.remove().


Mais de quoi tu parles [:mlc]

Sve@r a écrit :

Mais quelque part ça me gêne de devoir dupliquer mon tableau pour le remover. Ca peut pas marcher si je crée un indice perso qui n'évolue que quand il faut style

Code :
  1. i=0
  2. lg=len(tab)
  3. for j in range(lg):
  4.    if tab[i] != ce que je veux:
  5.        del tab[i]
  6.    else
  7.        i+=1


???


T'arriveras sûrement à le faire marcher, mais c'est immonde et c'est pas comme ça que code un utilisateur de python [:spamafote]


---------------
Stick a parrot in a Call of Duty lobby, and you're gonna get a racist parrot. — Cody
Reply

Marsh Posté le 16-10-2007 à 07:44:28    

masklinn a écrit :


Mais de quoi tu parles [:mlc]


Ben le tableau "out" est bien un second tableau qui contient les éléments de "tab" dont je ne veux pas (en fait, dans ton cas il contient les éléments que je veux mais c'est un détail).
Donc si je fais un "for c in out : tab.remove(c)" cela supprimera bien de "tab" les éléments que je voulais supprimer ? En tout cas c'est comme ça que j'ai vu ton exemple.
 

masklinn a écrit :


T'arriveras sûrement à le faire marcher, mais c'est immonde


Reçu. Je ferai pas comme ça.
 

masklinn a écrit :

et c'est pas comme ça que code un utilisateur de python [:spamafote]


Ah ben jdébute et j'ai pas encore toutes les subtilités en tête  :sol:  
 


---------------
Vous ne pouvez pas apporter la prospérité au pauvre en la retirant au riche.
Reply

Marsh Posté le 16-10-2007 à 08:05:04    

Sve@r a écrit :


Ben le tableau "out" est bien un second tableau qui contient les éléments de "tab" dont je ne veux pas (en fait, dans ton cas il contient les éléments que je veux mais c'est un détail).
Donc si je fais un "for c in out : tab.remove(c)" cela supprimera bien de "tab" les éléments que je voulais supprimer ? En tout cas c'est comme ça que j'ai vu ton exemple.


Heuu oui, mais vu que out contient les éléments que tu veux, tu peux surtout utiliser out directement tu sais [:pingouino]


---------------
Stick a parrot in a Call of Duty lobby, and you're gonna get a racist parrot. — Cody
Reply

Marsh Posté le 16-10-2007 à 23:32:40    

masklinn a écrit :


Heuu oui, mais vu que out contient les éléments que tu veux, tu peux surtout utiliser out directement tu sais [:pingouino]


Ben c'est un poil plus compliqué que ça. En fait, je suis en train de découvrir PyQt. Donc j'ai fait pour tester une appli PyQT avec un bouton qui lance une sous-fenêtre.

Code :
  1. #!/usr/bin/env python
  2. # coding: Latin-1 -*-
  3. import sys
  4. from PyQt4.QtCore import *
  5. from PyQt4.QtGui import *
  6.  
  7. class QtAppli(QApplication):
  8.     "Fenêtre de l'application"
  9.  
  10.     # Constructeur fenêtre
  11.     def __init__(self,
  12.             argv):
  13.  
  14.         # Appel constructeur de l'objet hértié
  15.         QApplication.__init__(self, argv)
  16.  
  17.         # Attributs de l'application
  18.         self.argv=argv
  19.  
  20.         # Widget principale
  21.         self.wid=QMainWindow()
  22.         self.wid.setCentralWidget(QWidget(self.wid))
  23.         self.wid.statusBar()
  24.  
  25.         # Titre
  26.         self.wid.setWindowTitle("Test QT" )
  27.  
  28.         # Un espace de rangement
  29.         box=QVBoxLayout(self.wid.centralWidget())
  30.  
  31.         # Le bouton
  32.         btn=QPushButton(self.wid.centralWidget())
  33.         btn.setText("Lancement sous-fenêtre" )
  34.         self.connect(btn, SIGNAL("clicked()" ), self.slotAction)
  35.         box.addWidget(btn)
  36.  
  37.         # Pour quitter
  38.         quit=QPushButton(self.wid.centralWidget())
  39.         quit.setText("Quitter" )
  40.         self.connect(quit, SIGNAL("clicked()" ), self, SLOT("quit()" ))
  41.         box.addWidget(quit)
  42.  
  43.     # Affichage et lancement application
  44.     def run(self):
  45.         self.wid.show()
  46.         self.exec_()
  47.  
  48.     # Slot qui affiche une fenêtre avec un texte
  49.     def slotAction(self):
  50.         print "clicked"
  51.  
  52.         dial=QtSub(self.wid.centralWidget())
  53.         dial.show()
  54.  
  55. class QtSub(QDialog):
  56.     "Sous-fenêtre"
  57.  
  58.     # Constructeur fenêtre
  59.     def __init__(self, parent):
  60.  
  61.         # Appel constructeur de l'objet hértié
  62.         QDialog.__init__(self, parent)
  63.  
  64.         self.setWindowTitle("Sous-fenêtre" )
  65.  
  66.         layout=QVBoxLayout(self)
  67.  
  68.         lab=QLabel("<center><font size='+5'>Clicked !!!</font></center>" )
  69.         layout.addWidget(lab)
  70.  
  71.         btn=QPushButton(self)
  72.         btn.setText("Fermer" )
  73.         btn.connect(btn, SIGNAL("clicked()" ), self, SLOT("close()" ))
  74.         layout.addWidget(btn)
  75.  
  76. Appli=QtAppli(sys.argv)
  77. Appli.run()


 
Ca marche bien... sauf que la sous-fenêtre, quoi qu'on fasse, reste toujours affichée au-dessus de la fenêtre principale.
 
J'en ai conclu que c'était peut-être parce que j'appelais la sous-fenêtre en lui passant la fenêtre principale comme parent. J'ai donc corrigé ça et mis "None" comme parent

Code :
  1. # Slot qui affiche une fenêtre avec un texte
  2.     def slotAction(self):
  3.         print "clicked"
  4.  
  5.         dial=QtSub(None)
  6.         dial.show()


 
Là, la fenêtre apparaît puis disparaît très vite. J'en ai conclu que la variable "dial" était libérée par le gc d'où la modif suivante

Code :
  1. # Slot qui affiche une fenêtre avec un texte
  2.     def slotAction(self):
  3.         print "clicked"
  4.  
  5.         self.dial=QtSub(None)
  6.         self.dial.show()


 
Là, c'est presque parfait sauf que si je lance deux ou plusieurs sous-fenêtres, la nouvelle remplace l'ancienne. Donc j'en suis venu à passer par un tableau (initialisé dans le constructeur)

Code :
  1. # Slot qui affiche une fenêtre avec un texte
  2.     def slotAction(self):
  3.         print "clicked"
  4.  
  5.         self.tabDial.append(QtSub(None))
  6.         self.tabDial[-1].show()


 
Là, tout est ok. Sauf que si l'utilisateur ferme une des "n" sous-fenêtres ouvertes, ben elles sont juste fermées mais restent toujours allouées d'où mon idée, dans le slotAction() de commencer par balayer le tabDial et si sa propriété "setVisible()" est à False, cela signifie que la fenêtre a été soit fermée par le bouton soit par la croix et donc de supprimer ces fenêtres devenues inutiles via un tabDial.remove(). ce qui m'a amené au post initial de ce topic (et qui recroise aussi le topic où je cherchais à comprende le gc)...
 
 
 
 


---------------
Vous ne pouvez pas apporter la prospérité au pauvre en la retirant au riche.
Reply

Marsh Posté le 19-10-2007 à 21:35:08    

Si les solutions de Masklinn conviennent pas pour ton problème, tu peux aussi passer par deux boucles :

Code :
  1. values_to_remove = [value for value in self.tabDial if not value.setVisible()]
  2.  
  3. for value in values_to_remove:
  4.    self.tabDial.remove(value)


... en gros.
 
Arpsè, j'y connais rien en PyQt, mais ton problème à l'air un peu bizarre quand même. Ya peut-être un problème ailleurs (mais bon, j'y connais rien [:cosmoschtroumpf] )

Reply

Marsh Posté le 20-10-2007 à 22:09:28    

multani a écrit :

Si les solutions de Masklinn conviennent pas pour ton problème, tu peux aussi passer par deux boucles :

Code :
  1. values_to_remove = [value for value in self.tabDial if not value.setVisible()]
  2.  
  3. for value in values_to_remove:
  4.    self.tabDial.remove(value)


... en gros.


Oui, c'est ce que j'avais dit dans mon 2° post. Créer un 2° tableau contenant les valeurs à supprimer puis traiter ces valeurs via le premier tableau...
 

multani a écrit :

Après, j'y connais rien en PyQt, mais ton problème à l'air un peu bizarre quand même. Ya peut-être un problème ailleurs (mais bon, j'y connais rien [:cosmoschtroumpf] )


Ben moi aussi je débute avec Qt donc je tatonne un peu...


---------------
Vous ne pouvez pas apporter la prospérité au pauvre en la retirant au riche.
Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed