Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

vitesse manipulation liste (generateur ?)

14 réponses
Avatar
Lulu
Bonjour,

J'ai besoin de manipuler des listes rapidement pour manipuler une image.
D'une liste ((r,v,b),(a,b,c),(x,y,z),.....), je veux passer à trois
listes :
((r,0,0),(a,0,0),(x,0,0),....)
((0,v,0),(0,b,0),(0,y,0),....)
((0,0,b),(0,0,c),(0,0,z),....)

Et je veux faire ça le plus rapidement possible.
J'imagine que ça pourrait passer par un générateur, mais je ne sais pas
comment en "fabriquer" et les exemples que je trouve sur le web ne
m'aident pas vraiment. (plus essais infructueux).


Mon code :
8<-----------8<---------8<----------8<----------8<----------8<----------8<
#!/usr/bin/python3
# -*- coding: utf-8 -*-

from PIL import Image

Fichier_Image = "Anna_et_Lulu.jpg"

ze_image = Image.open( Fichier_Image ).convert("RGB")
ze_image.show()

(l, h) = ze_image.size

# création de l'image rouge
ze_image_rouge = Image.new("RGB",(l,h))

# traitement pixel par pixel :
for y in range(h): # pour y variant de 0 à h-1
for x in range(l): # pour x variant de 0 à l-1
(rouge, vert, bleu) = ze_image.getpixel((x,y))
ze_image_rouge.putpixel((x,y), ( rouge, 0, 0))

# création en mémoire de l'image rouge
ze_image_rouge_fast = Image.new("RGB",(l,h))

liste_pixels = list(ze_image.getdata())
liste_pixels_rouges=[]

for i in range(len(liste_pixels)):
liste_pixels_rouges.append((liste_pixels[i][0], 0, 0))

ze_image_rouge_fast.putdata(liste_pixels_rouges)

ze_image_rouge.show()
ze_image_rouge_fast.show()
8<-----------8<---------8<----------8<----------8<----------8<----------8<

Sur une image de 1.600.000 pixels, la première double boucle avec deux
for imbriqués bricole pendant 7 secondes (pas très étonnant), mais la
deuxième boucle qui remplit trois listes n'est que 4 fois plus rapide.

Je crois que c'est en jouant sur ma manière de créer ces trois listes et
de les remplir que je peux gagner le plus.

Merci de vos avis

4 réponses

1 2
Avatar
Nicolas
Le 28/03/2020 à 19:15, Lulu a écrit :
Mais comprendre zip(), c'était aussi un but pédagogique, mais pour moi
seul ;-)

Dans ce cas, rien à dire. ;)
Je ne donnerai mon code source qu'aux quelques geeks de mes 120 élèves
qui me poseraient des questions sur la manipulation des listes...
Mon fils a pris la spécialisation NSI (il est en seconde).

Euh... NSI (Numérique et Sciences Informatiques), c'est en première.
Je suis prof de seconde en Physique/Chimie et SNT (Sciences Numériques
et Technologie)

Oups, désolé. Il est en première :p
Qu'elle est la différence entre
l = [1, 3, 5, 9]
for e in l :
print(e)
et
l = [1, 3, 5, 9]
for i in range(len(l)) :
print(l[i])
Dans quel cas faut-il utiliser la 1ere forme ? La deuxième forme ?

Ça, j'ai su mais j'ai oublié...
Peux-tu rafraichir ma mémoire ?

Les deux exemples font exactement la même chose. Ils affichent les
éléments de la liste "l".
Dans le premier exemple, on itère sur les éléments de la liste.
Avec "for e in l", e vaut successivement 1, 3, 5 et 9.
On peut afficher l'élément de la liste directement avec "print(e)"
Dans le second exemple, on itère sur les indices des éléments de la liste.
Avec "for i in range(len(l))", i vaut successivement 0, 1, 2 et 3.
Du coup, pour afficher l'élément de la liste on ne fait pas "print(e)"
mais "print(l[i])".
En Python, la première forme est préférable.
Pourquoi s’embêter avec des indices quand il n'y en a pas besoin ?
En plus, ça évite les bugs dû aux erreurs de gestion des indices.
Mais, parfois, on a besoin de l'indice. Par exemple, pour afficher :
0 -> 1
1 -> 3
2 -> 5
3 -> 9
On écrira :
for i in range(len(l)) :
print(i, "->", l[i])
Pas le choix, il faut l'indice.
On peut faire mieux en écrivant :
for i,e in enumerate(l) :
print(i, "->", e)
"enumerate()" renvoie des tuples contenant l'indice et l'élément
correspondant de la liste.
L'écriture est plus concise et le risque de bug est réduit :
"enumerate(l) " est plus simple que "range(len(l))" et on a i et e en phase.
Bon week-end
Avatar
Nicolas
Le 28/03/2020 à 18:08, Lulu a écrit :
J'avais aussi essayé la "compréhension de liste" (par exemple ici :
https://openclassrooms.com/fr/courses/1206331-utilisation-avancee-des-listes-en-python
), mais je n'ai rien compris...

La compréhension de liste, c'est pas compliqué.
----------------------
Soit le code suivant :
l_in = [1, 45, 89, 56, 4]
l_out = []
for e in l_in :
l_out.append(e)
C'est exactement la même chose que :
l_out = [e for e in l_in]
--------------------------
On peut compléter un peu :
l_out = []
for e in l_in :
l_out.append(traitement(e))
est équivalent à :
l_out = [traitement(e) for e in l_in]
------------------
Version complète :
l_out = []
for e in l_in :
if is_ok(e) :
l_out.append(traitement(e))
est équivalent à :
l_out = [traitement(e) for e in l_in if is_ok(e)]

où j'imagine que traitement() et ok() sont deux fonctions ?

Tout à fait. Des fonctions à toi que tu as :)
C'était un exemple, l'utilisation de fonctions n'est pas obligatoire.
Un exemple sans fonction :
l_out = [e*2 for e in l_in if e > 10]
Les 4 lignes sont "mises à plat" en une seule ligne.
Ca serait plus facile à expliquer avec des couleurs ou un dessin mais,
sur la mailing list, c'est pas possible.

Donc pour mon exemple de liste de tuples.
D'une liste ((r,v,b),(a,b,c),(x,y,z),.....), je veux passer à trois
listes :
((r,0,0),(a,0,0),(x,0,0),....)
((0,v,0),(0,b,0),(0,y,0),....)
((0,0,b),(0,0,c),(0,0,z),....)




j'obtiens mes 3 listes avec l1 = [ (e[0], 0, 0) for e in l_in ]
l2 = [ (0, e[1], 0) for e in l_in ] et l3 = [ (0, 0, e[2]) for e in l_in ]

C'est ça :)
(Bizarrement, c'est aussi l'exemple de zip(list) et zip(*list)) donné
par Pierre qui m'a aussi permis de comprendre ;-)
Donc mille mercis encore à vous deux.
Je viens de tester dans mon script et la manipulation de liste par
compréhension est la plus rapide des 4 manipulations que je tente :
-=-=-=-
Image : ws_SBK-07_0834.jpg
taille de l'image : 1024 px × 768 px
nombre de pixels : 786432
-=-=-=-
1: Traitement par une boucle sur les 786432 pixels.
durée = 9.874 seconde(s)
-=-=-=-
2: Traitement par boucle avec list.append().
durée = 2.402 seconde(s)
4 fois plus rapide.
-=-=-=-
3: Traitement par compréhension de liste.
durée = 1.495 seconde(s)
6 fois plus rapide.
-=-=-=-
4: Traitement par split avec zip(*liste) et merge par zip(liste).
durée = 2.786 seconde(s)
3 fois plus rapide.
-=-=-=-
5: Traitement par split de liste et fausse compréhension
durée = 2.688 seconde(s)
3 fois plus rapide.
-=-=-=-
6: Traitement par Image.split().
durée = 0.002264 seconde(s)
4361 fois plus rapide.

Image.split() n'est pas écrit en Python mais en C. Impossible à battre ;)
Mon code :
8<-----------8<---------8<----------8<----------8<----------8<----------8<
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import sys
from datetime import datetime
from PIL import Image, ImageFilter
# Fichier_Image = "Les_Horribles_Cernettes.png"
# Fichier_Image = "avengers-endseries-comics.png"
# Fichier_Image = "Anna_et_Lulu.jpg"
Fichier_Image = "ws_SBK-07_0834.jpg"
# Fichier_Image = "Sauzon.jpg"
print("-=-=-=-nImage : ", Fichier_Image)
try:
ze_image = Image.open( Fichier_Image ).convert("RGB")
except:
print('n' * 2)
print(" Erreur : le fichier n",Fichier_Image,"" existe-t-il ?")
print('n' * 2)
sys.exit(1)
# j'appelle la méthode 'show' de la classe PIL.Image.Image (liste des méthodes help(Image))
ze_image.show()
(l, h) = ze_image.size
print("taille de l'image :", l, "px ×", h, "px")
nb_pixels = l*h
print("nombre de pixels :",nb_pixels)
# 1er procédé
# création en mémoire des 3 images à calculer
ze_image_rouge_p1 = Image.new("RGB",(l,h))
ze_image_verte_p1 = Image.new("RGB",(l,h))
ze_image_bleue_p1 = Image.new("RGB",(l,h))
# mesure du temps de traitement :
timestamp_debut = datetime.timestamp(datetime.now())
# traitement pixel par pixel :
print("-=-=-=-n1: Traitement par une boucle sur les", nb_pixels,"pixels.")
for y in range(h): # pour y variant de 0 à h-1
for x in range(l): # pour x variant de 0 à l-1
(rouge, vert, bleu) = ze_image.getpixel((x,y))
ze_image_rouge_p1.putpixel((x,y), ( rouge, 0, 0))
ze_image_verte_p1.putpixel((x,y), ( 0, vert, 0))
ze_image_bleue_p1.putpixel((x,y), ( 0, 0, bleu))
# mesure du temps de traitement :
timestamp_fin = datetime.timestamp(datetime.now())
duree_1 = timestamp_fin - timestamp_debut
print('durée =',"{:0.3f}".format(duree_1),"seconde(s)")
# 2ème procédé
# création en mémoire des 3 images à calculer
ze_image_rouge_p2 = Image.new("RGB",(l,h))
ze_image_verte_p2 = Image.new("RGB",(l,h))
ze_image_bleue_p2 = Image.new("RGB",(l,h))
liste_pixels = []
liste_pixels_rouges = []
liste_pixels_verts =[]
liste_pixels_bleus = []
print("-=-=-=-n2: Traitement par boucle avec list.append().")
timestamp_debut = datetime.timestamp(datetime.now())
liste_pixels = list(ze_image.getdata())
for i in range(len(liste_pixels)):
liste_pixels_rouges.append((liste_pixels[i][0], 0, 0))
liste_pixels_verts.append((0, liste_pixels[i][1], 0))
liste_pixels_bleus.append((0, 0, liste_pixels[i][2]))
ze_image_rouge_p2.putdata(liste_pixels_rouges)
ze_image_verte_p2.putdata(liste_pixels_verts)
ze_image_bleue_p2.putdata(liste_pixels_bleus)
timestamp_fin = datetime.timestamp(datetime.now())
duree_2 = timestamp_fin - timestamp_debut
print('durée =',"{:0.3f}".format(duree_2),"seconde(s)")
print(int(duree_1/duree_2), "fois plus rapide.")
# 3ème procédé
# création en mémoire des 3 images à calculer
ze_image_rouge_p3 = Image.new("RGB",(l,h))
ze_image_verte_p3 = Image.new("RGB",(l,h))
ze_image_bleue_p3 = Image.new("RGB",(l,h))
liste_pixels = []
# liste_pixels_rouges = []
liste_pixels_verts =[]
liste_pixels_bleus = []
print("-=-=-=-n3: Traitement par compréhension de liste.")
timestamp_debut = datetime.timestamp(datetime.now())
liste_pixels = list(ze_image.getdata())
#print("liste_pixels[:30] = ", liste_pixels[:30])
liste_pixels_rouges = [ (e[0], 0, 0) for e in liste_pixels ]
liste_pixels_verts = [ (0, e[1], 0) for e in liste_pixels ]
liste_pixels_bleus = [ (0, 0, e[2]) for e in liste_pixels ]
ze_image_rouge_p3.putdata(liste_pixels_rouges)
ze_image_verte_p3.putdata(liste_pixels_verts)
ze_image_bleue_p3.putdata(liste_pixels_bleus)
timestamp_fin = datetime.timestamp(datetime.now())
duree_3 = timestamp_fin - timestamp_debut
print('durée =',"{:0.3f}".format(duree_3),"seconde(s)")
print(int(duree_1/duree_3), "fois plus rapide.")
# 4ème procédé
# création en mémoire des 3 images à calculer
ze_image_rouge_p4 = Image.new("RGB",(l,h))
ze_image_verte_p4 = Image.new("RGB",(l,h))
ze_image_bleue_p4 = Image.new("RGB",(l,h))
liste_pixels = []
liste_pixels_rouges = []
liste_pixels_verts =[]
liste_pixels_bleus = []
liste_pixels_zero = []
print("-=-=-=-n4: Traitement par split avec zip(*liste) et merge par zip(liste).")
timestamp_debut = datetime.timestamp(datetime.now())
liste_pixels = list(ze_image.getdata())
liste_pixels_rouges, liste_pixels_verts, liste_pixels_bleus = list(zip(*liste_pixels))
liste_pixels_zero = [0] * nb_pixels
ze_image_rouge_p4.putdata(list(zip(liste_pixels_rouges, liste_pixels_zero, liste_pixels_zero)))
ze_image_verte_p4.putdata(list(zip(liste_pixels_zero, liste_pixels_verts, liste_pixels_zero)))
ze_image_bleue_p4.putdata(list(zip(liste_pixels_zero, liste_pixels_zero, liste_pixels_bleus)))
timestamp_fin = datetime.timestamp(datetime.now())
duree_4 = timestamp_fin - timestamp_debut
print('durée =',"{:0.3f}".format(duree_4),"seconde(s)")
print(int(duree_1/duree_4), "fois plus rapide.")
# 5ème procédé
liste_pixels = []
liste_pixels_rouges = []
liste_pixels_verts =[]
liste_pixels_bleus = []
liste_pixels_zero = []
ze_image_rouge_p5 = Image.new("RGB",(l,h))
ze_image_verte_p5 = Image.new("RGB",(l,h))
ze_image_bleue_p5 = Image.new("RGB",(l,h))
print("-=-=-=-n5: Traitement par split de liste et fausse compréhension")
timestamp_debut = datetime.timestamp(datetime.now())
liste_pixels = list(ze_image.getdata())
liste_pixels_rouges, liste_pixels_verts, liste_pixels_bleus = list(zip(*liste_pixels))
liste_pixels_rouges = [(x, 0, 0) for x in liste_pixels_rouges ]
liste_pixels_verts = [(0, x, 0) for x in liste_pixels_verts ]
liste_pixels_bleus = [(0, 0, x) for x in liste_pixels_bleus ]
ze_image_rouge_p5.putdata(liste_pixels_rouges)
ze_image_verte_p5.putdata(liste_pixels_verts)
ze_image_bleue_p5.putdata(liste_pixels_bleus)
timestamp_fin = datetime.timestamp(datetime.now())
duree_5 = timestamp_fin - timestamp_debut
print('durée =',"{:0.3f}".format(duree_5),"seconde(s)")
print(int(duree_1/duree_5), "fois plus rapide.")
# 6ème procédé
print("-=-=-=-n6: Traitement par Image.split().")
timestamp_debut = datetime.timestamp(datetime.now())
ze_image_splited = Image.Image.split(ze_image)
timestamp_fin = datetime.timestamp(datetime.now())
duree_6 = timestamp_fin - timestamp_debut
print('durée =',"{:0.6f}".format(duree_6),"seconde(s)")
print(int(duree_1/duree_6), "fois plus rapide.")
x = input("Afficher les images [O/n] ? ")
if x == "" or x[0] == "o" or x[0] == "O" :
ze_image_rouge_p1.show()
ze_image_verte_p1.show()
ze_image_bleue_p1.show()
ze_image_rouge_p2.show()
ze_image_verte_p2.show()
ze_image_bleue_p2.show()
ze_image_rouge_p3.show()
ze_image_verte_p3.show()
ze_image_bleue_p3.show()
ze_image_rouge_p4.show()
ze_image_verte_p4.show()
ze_image_bleue_p4.show()
ze_image_rouge_p5.show()
ze_image_verte_p5.show()
ze_image_bleue_p5.show()
ze_image_splited[0].show()
ze_image_splited[1].show()
ze_image_splited[2].show()
Avatar
Lulu
Le 28-03-2020, Nicolas a écrit :
Le 28/03/2020 à 19:15, Lulu a écrit :
Qu'elle est la différence entre
l = [1, 3, 5, 9]
for e in l :
print(e)
et
l = [1, 3, 5, 9]
for i in range(len(l)) :
print(l[i])
Dans quel cas faut-il utiliser la 1ere forme ? La deuxième forme ?

Ça, j'ai su mais j'ai oublié...
Peux-tu rafraichir ma mémoire ?

Les deux exemples font exactement la même chose. Ils affichent les
éléments de la liste "l".
Dans le premier exemple, on itère sur les éléments de la liste.
Avec "for e in l", e vaut successivement 1, 3, 5 et 9.
On peut afficher l'élément de la liste directement avec "print(e)"
Dans le second exemple, on itère sur les indices des éléments de la liste.
Avec "for i in range(len(l))", i vaut successivement 0, 1, 2 et 3.
Du coup, pour afficher l'élément de la liste on ne fait pas "print(e)"
mais "print(l[i])".
En Python, la première forme est préférable.

OK.
Et j'ai testé, la première forme est aussi plus rapide de 30% environ.
Pourquoi s’embêter avec des indices quand il n'y en a pas besoin ?

Effectivement.
En plus, ça évite les bugs dû aux erreurs de gestion des indices.
Mais, parfois, on a besoin de l'indice. Par exemple, pour afficher :
0 -> 1
1 -> 3
2 -> 5
3 -> 9
On écrira :
for i in range(len(l)) :
print(i, "->", l[i])
Pas le choix, il faut l'indice.
On peut faire mieux en écrivant :
for i,e in enumerate(l) :
print(i, "->", e)
"enumerate()" renvoie des tuples contenant l'indice et l'élément
correspondant de la liste.
L'écriture est plus concise et le risque de bug est réduit :
"enumerate(l) " est plus simple que "range(len(l))" et on a i et e en phase.

OK, je note le enumerate()
Bon week-end

Tout pareil.
Merci pour tes réponses instructives.
Avatar
ast
Le 27/03/2020 à 00:50, Lulu a écrit :
Bonjour,
J'ai besoin de manipuler des listes rapidement pour manipuler une image.
D'une liste ((r,v,b),(a,b,c),(x,y,z),.....), je veux passer à trois
listes :
((r,0,0),(a,0,0),(x,0,0),....)
((0,v,0),(0,b,0),(0,y,0),....)
((0,0,b),(0,0,c),(0,0,z),....)
Et je veux faire ça le plus rapidement possible.
J'imagine que ça pourrait passer par un générateur, mais je ne sais pas
comment en "fabriquer" et les exemples que je trouve sur le web ne
m'aident pas vraiment. (plus essais infructueux).
Mon code :
8<-----------8<---------8<----------8<----------8<----------8<----------8<
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from PIL import Image
Fichier_Image = "Anna_et_Lulu.jpg"
ze_image = Image.open( Fichier_Image ).convert("RGB")
ze_image.show()
(l, h) = ze_image.size
# création de l'image rouge
ze_image_rouge = Image.new("RGB",(l,h))
# traitement pixel par pixel :
for y in range(h): # pour y variant de 0 à h-1
for x in range(l): # pour x variant de 0 à l-1
(rouge, vert, bleu) = ze_image.getpixel((x,y))
ze_image_rouge.putpixel((x,y), ( rouge, 0, 0))
# création en mémoire de l'image rouge
ze_image_rouge_fast = Image.new("RGB",(l,h))
liste_pixels = list(ze_image.getdata())
liste_pixels_rouges=[]
for i in range(len(liste_pixels)):
liste_pixels_rouges.append((liste_pixels[i][0], 0, 0))
ze_image_rouge_fast.putdata(liste_pixels_rouges)
ze_image_rouge.show()
ze_image_rouge_fast.show()
8<-----------8<---------8<----------8<----------8<----------8<----------8<
Sur une image de 1.600.000 pixels, la première double boucle avec deux
for imbriqués bricole pendant 7 secondes (pas très étonnant), mais la
deuxième boucle qui remplit trois listes n'est que 4 fois plus rapide.
Je crois que c'est en jouant sur ma manière de créer ces trois listes et
de les remplir que je peux gagner le plus.
Merci de vos avis

L = [(1,2,3),(11,12,13),(21,22,23),(31,32,33),(411,412,413),(521,522,523)]
g1 = ((x, 0, 0) for x,y,z in L)
g2 = ((0, y, 0) for x,y,z in L)
g1 = ((0, 0, Z) for x,y,z in L)
next(g1)
(1, 0, 0)
next(g1)
(11, 0, 0)
for _, y, _ in g2:
print(y)
2
12
22
32
412
522
1 2