Différence entre bouts de codes

Résolu
pycarpe Messages postés 19 Statut Membre -  
mamiemando Messages postés 33228 Date d'inscription   Statut Modérateur Dernière intervention   -
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

  1. mamiemando Messages postés 33228 Date d'inscription   Statut Modérateur Dernière intervention   7 944
     
    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
  2. mamiemando Messages postés 33228 Date d'inscription   Statut Modérateur Dernière intervention   7 944
     
    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
  3. pycarpe Messages postés 19 Statut Membre
     
    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
  4. pycarpe Messages postés 19 Statut Membre
     
    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
    1. mamiemando Messages postés 33228 Date d'inscription   Statut Modérateur Dernière intervention   7 944
       
      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
  5. Vous n’avez pas trouvé la réponse que vous recherchez ?

    Posez votre question
  6. pycarpe Messages postés 19 Statut Membre
     
    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
    1. mamiemando Messages postés 33228 Date d'inscription   Statut Modérateur Dernière intervention   7 944
       
      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