Différence entre bouts de codes

Résolu/Fermé
pycarpe Messages postés 16 Date d'inscription lundi 24 janvier 2022 Statut Membre Dernière intervention 9 août 2022 - 26 janv. 2022 à 13:53
mamiemando Messages postés 33093 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 4 mai 2024 - 27 janv. 2022 à 12:34
Bonjour,

En premier lieu, je voudrais savoir si il y a une différence de résultat entre ces deux bouts de code :

1)

self.filename = ["{path}image1, {path}image2]
self.image_index  = 0
 
self.images = [
            tk.PhotoImage(file=f, master=self)
            for f in filename
        ]
 
    def update_image(self):
        if self.images:
            image = self.images[self.image_index]
            self.canvas_logo.itemconfigure(self.image_container, image=image)
            self.canvas_logo.pack()
 
    def on_update(self):
        if self.images:
            self.image_index += 1
            self.image_index %= len(self.images)
            self.update_image()
            self.after(500, self.on_update)


2)

self.filename = ["{path}image1, {path}image2]
self.image_index  = 0
 
    def update_image(self):
        if self.filename:
            image = tk.PhotoImage(file=filename[self.image_index], master=self)
            self.canvas_logo.itemconfigure(self.image_container, image=image)
            self.canvas_logo.pack()
 
    def on_update(self):
        if self.filename:
            self.image_index += 1
            self.image_index %= len(self.filename)
            self.update_image()
            self.after(500, self.on_update)


-Si la réponse est non il n'y a pas de différence alors j'aimerai savoir pourquoi =>:

Dans le 1er code l'image change bien alors que dans le 2e code aucune image s'affiche?

-Si la réponse est oui il y a une différence alors j'aimerai savoir laquelle svp.

Merci par avance.

5 réponses

mamiemando Messages postés 33093 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 4 mai 2024 7 752
26 janv. 2022 à 14:33
Bonjour,

Pour toute personne qui tombe sur cette discussion, le code montré est extrait de ce message.

Ici, le code extrait n'a pas de sens seul, car self n'a de sens que si cette variable est défini, ce qui est le cas quand tu travailles dans une classe (le premier paramètre, qui pointe sur l'instance courante de la classe, est noté par convention
self
). Ici le code que tu as extrait de matérialise ni la classe, ni les méthodes, et donc est invalide en tant que tel dans les deux cas. De plus l'indentation, importante en python, n'est pas respectée dans les deux extraits de code que tu reportes.

Je vais donc supposer que le code est donc plutôt :

code1.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import tkinter as tk

class Application:
    def __init__(self):
        self.filename = ["{path}image1, {path}image2]
        self.image_index  = 0
        self.images = [
            tk.PhotoImage(file=f, master=self)
            for f in filename
        ]
 
    def update_image(self):
        if self.images:
            image = self.images[self.image_index]
            self.canvas_logo.itemconfigure(self.image_container, image=image)
            self.canvas_logo.pack()
 
    def on_update(self):
        if self.images:
            self.image_index += 1
            self.image_index %= len(self.images)
            self.update_image()
            self.after(500, self.on_update)


code2.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import tkinter as tk

class Application:
    def __init__(self):
        self.filename = ["{path}image1, {path}image2]
        self.image_index  = 0
 
    def update_image(self):
        if self.filename:
            image = tk.PhotoImage(file=filename[self.image_index], master=self)
            self.canvas_logo.itemconfigure(self.image_container, image=image)
            self.canvas_logo.pack()
 
    def on_update(self):
        if self.filename:
            self.image_index += 1
            self.image_index %= len(self.filename)
            self.update_image()
            self.after(500, self.on_update)


Il y a trois différences, la seconde et la troisième étant conséquence de la première :
  • Sous ces conditions, dans
    code1.py
    la classe
    Application
    possède un attribut
    self.images
    , ce qui n'est pas le cas dans
    code2.py
    . Algorithmiquement parlant c'es la seule différence.
  • Mais d'un point de vue Tk, il y a une autre différence : pour Tk, les images ne sont pas "chargées" dans le même environnement et donc ton application risque de ne pas afficher correctement les images (voir ce message). C'est pourquoi avec
    code2.py
    les images risquent de ne pas s'afficher correctement. C'est quelque chose d'assez contre intuitif, de spécifique à la manière dont fonctionne Tk, et qu'il faut "admettre".
  • Du point de vue de machine, dans
    code1.py
    les fichiers images sont lus depuis le disque dur une et une seule fois. Ensuite les images sont en mémoire. Dans
    code2.py
    ils sont relus à chaque appel de
    on_update()
    , soit autant de lectures faites sur le disque dur. Si cela est négligeable pour de petits fichiers et une fréquence faible, il faut garder en tête qu'une opération de lecture ou d'écriture est une opération extrêmement lente en informatique (bien plus qu'un accès en mémoire), sans parler du fait tu sollicites davantage ton disque dur (et donc son usure).


Bonne chance
1
mamiemando Messages postés 33093 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 4 mai 2024 7 752
Modifié le 26 janv. 2022 à 16:47
Donc le fait de générer directement une double instance de Photoimage ...

En fait ça n'est pas une double instance. Quand tu appelles
tk.PhotoImage()
cela crée une instance en mémoire. Le fait de stocker cette instance dans une liste ne la duplique pas. En réalité, la liste stocke juste une référence vers l'instance existante. La fonction
id()
permet de récupérer l'adresse mémoire sous d'une instance. C'est quelque chose que tu peux contrôler dans le constructeur.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import tkinter as tk
import time
import os

IMAGE_DIR = "/home/mando/Downloads"
 
def find(dirname :str):
    return (
        os.path.join(root, name)
        for (root, dirs, files) in os.walk(dirname, topdown=False)
        for name in files
    )

class Application(tk.Frame):
    def __init__(self, filenames=None, master=None):
        tk.Frame.__init__(self, master)
        self.pack(expand=True, fill="both")
        self.entries_y = 30
        self.images = list()
        for filename in filenames:
            image = tk.PhotoImage(file=filename, master=self if master is None else master)
            self.images.append(image)
            print(f"image: {filename} {hex(id(image))}")
        print(f"self.images: {[hex(id(image)) for image in self.images]}")
        self.image_index = 0

# [...]

root = tk.Tk()
root.geometry("1000x720")
app = Application(
    filenames = [
        filename
        for filename in find(IMAGE_DIR)
        if filename.endswith(".gif")
    ],
    master = root
)
root.mainloop()


Dans mon cas, le dossier
/home/mando/Downloads/
contient 3 fichiers
image1.gif
,
image2.gif
,
image3.gif
et en exécutant ce programme, j'obtiens :

image: /home/mando/Downloads/image3.gif 0x7fbeafd17fd0
image: /home/mando/Downloads/image2.gif 0x7fbeaffe4220
image: /home/mando/Downloads/image1.gif 0x7fbeaffcf7c0
self.images: ['0x7fbeafd17fd0', '0x7fbeaffe4220', '0x7fbeaffcf7c0']


Comme tu peux le voir, les adresses mémoires de chaque image correspond aux adresses mémoires stockées dans
self.images
.

Petite recommandation dans ton code, attention aux pluriels (
filename
= 1 chemin, donc une chaîne de caractères ;
filenames
= plusieurs chaînes de caractères, donc par exemple une liste), idem pour les images. Cela t'évitera de te mélanger les pinceaux :-)

Bonne chance
1
pycarpe Messages postés 16 Date d'inscription lundi 24 janvier 2022 Statut Membre Dernière intervention 9 août 2022
26 janv. 2022 à 15:40
J'espérais que ce soit toi qui me réponde car j'ai pas trouvé mieux en terme d'altruisme.
Tout d'abord dsl pour les indentations, j'ai modifié certaines choses après le copié collé du coup j'ai du merdé à ce moment là. Pour les histoires de self j'aurais du mettre toute la classe directement tu as raison.

Donc le fait de générer directement une double instance de Photoimage et les stocker dans un tableau dès le départ dans le constructeur permet de mémoriser indirectement les images une seule fois pour pouvoir les récupérer quand on veut. Donc dans une classe, si on veut solliciter des fichiers plus tard pour les manipuler, il est préférable de générer des instances avec ces fichiers en arguments, plutôt que de récupérer les fichiers en dur.

Si tu me confirmes mes dires alors grâce à toi j'ai fait un grand pas en avant dans mon apprentissage crois moi..
0
pycarpe Messages postés 16 Date d'inscription lundi 24 janvier 2022 Statut Membre Dernière intervention 9 août 2022
Modifié le 26 janv. 2022 à 17:36
Pardon, bien sûr, quand je disais double instance, c'est deux référence de l' instance , une représentant self.images[0] et une représentant self.images[1] effectivement vu que c'est toujours sur une seule instance self.images . Manque de rigueur de ma part dans mes propos mais j'ai bien compris l'idée du coup. Je suis content d'avoir saisie cette notion maintenant plus qu'à pratiquer encore et encore.

PS: quand j'ai essayé "code2" j'avais bien récupéré l"id de la variable image dans update_image après chaque appel pour voir si ce n' était pas un problème d'adresse mémoire qui se marche dessus. J'ai juste omis volontairement de mettre cette ligne lors de mon post.

Merci énormément encore une fois pour cette masterclass en tout cas. Je déclare le sujet résolu. Bonne soirée :)
0
mamiemando Messages postés 33093 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 4 mai 2024 7 752
Modifié le 26 janv. 2022 à 17:45
Bonne soirée également :-)

Il est vraiment important de faire le plus tôt possible la distinction entre instance et référence (~ l'adresse mémoire de cette instance). C'est une notion qui revient dans d'autres langages (donc en Java, C++, ...).

En python, les types mutables peuvent être passés par référence à une fonction. Cela concerne la plupart des types (classes, list, set, dict, ...) mais pas les types de bases (int, float) qui sont immutables. Contrairement à d'autres langages (comme le C++ et le Java), les chaînes de caractères sont immutables.

Cela a un impact important. Une fonction peut modifier un paramètre mutable (reçu par référence). S'il reçoit un immutable, tout se passe "comme si" le paramètre était passé par copie.

Exemple : une liste est mutable (= référençable)

def f(l):
    l.append(4)

l = [1, 2, 3]
f(l)
print(l) # [1, 2, 3, 4]


Exemple : une chaîne de caractère est immutable :

def f(s):
    s += "toto"
    print(s) # "tatatoto"

s = "tata"
f(s)
print(s) # "tata", pas "tatatoto"


Bonne chance
1

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
pycarpe Messages postés 16 Date d'inscription lundi 24 janvier 2022 Statut Membre Dernière intervention 9 août 2022
Modifié le 26 janv. 2022 à 19:54
Dans le principe je comprend bien. Après il faut que j'apprenne à utiliser à mon avantage ce principe même suivant les situations. Pour ca il faut que je continue à pratiquer.

Merci beaucoup pour ce rappel.
Ok dans la fonction update_image je peux donc écrire en principe.

def update_image(self):
        if self.filename:
            self.image = tk.PhotoImage(file=filename[self.image_index], master=self)
            self.canvas_logo.itemconfigure(self.image_container, image=self.image)


Mais j'utiliserai ta méthode en déclarant l'objet directement dans le constructeur c'est plus propre et plus lisible.
0
mamiemando Messages postés 33093 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 4 mai 2024 7 752
27 janv. 2022 à 12:34
Oui, ça devrait marcher, car ce faisant,
self
maintient une référence vers l'image, que tu pourras ensuite réutiliser dans
on_update
.
1