IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Python Discussion :

Problème de compréhension du fonctionnement de del et des references ou copie


Sujet :

Python

  1. #1
    Membre éclairé
    Profil pro
    Inscrit en
    Avril 2004
    Messages
    574
    Détails du profil
    Informations personnelles :
    Âge : 41
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations forums :
    Inscription : Avril 2004
    Messages : 574
    Par défaut Problème de compréhension du fonctionnement de del et des references ou copie
    Bonjour,

    Je suis en train de développer une application utilisant Tkinter pour afficher divers articles dans une fenêtre.
    Chaque article est affiché à l'aide d'un label et d'une image par exemple.
    En fait j'ai une liste myElems dans laquelle je place tous ce que je vais devoir afficher.

    Par exemple myElems = [MyLabel, MyButtonImage, MyLabel2, MyButtonImage2...]

    Sachant que MyLabel étends par exemple la classe Tkinter.Label...

    Bref, ensuite je passe cette liste à une classe "Agenceur" qui dispose correctement tous mes éléments dans un frame (par exemple, je peux préciser combien je veux d'élement par colone et par ligne... je peux changer la couleur d'une ligne sur deux, etc...).

    Bref !

    Lorsque que de grosse modification sont réalisés quelque part, je recharge entièrement tous les articles.
    Pour cela, je fais d'abord un del myElems [:]
    Mais je n'ai pas le sentiment que la mémoire est vraiment relachée ! Car au bout d'un moment, très rapide, mon application plante pour cause qu'elle "ne peut pas allouer de mémoire pour bitmap".

    J'ai donc surchargé les fonction __del__ de ma classe MaLabel dans laquelle je mets juste un print pour voir si je passais bien dedans.

    Et je n'y passe pas en cours d'application. Si je ferme mon application avant qu'elle ne plante pour cause d'espace mémoire insuffisant, alors j'ai bien mes print qui sont réalisés.

    Venant du C++, j'ai du mal à comprendre quand le garbage collector fait son travail, si il est possible de le forcer à réaliser une libération d'espace mémoire immédiate...
    Ensuite, je souhaiterais savoir si lorsque l'on vide une liste, elle supprime automatiquement tous ces objets. Par exemple, si on les a passé avant à une autre liste, il ne devrait pas le faire ?
    Comment se passe exactement les transferts d'un objet dans une autre liste.
    Est ce une copie réelle ou alors il pointe vers la même référence ??

    Merci de m'éclairer sur ces points si c'est possible ...

    Par exemple, j'ai fait un simple text dans mon main :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    root = Tkinter.Tk()
    label = MyLabel(root)
    del label
    et c'est tout et je ne passe pas dans ma fonction del puisque rien n'est affiché... ???!!!

  2. #2
    Membre Expert

    Homme Profil pro
    Diverses et multiples
    Inscrit en
    Mai 2008
    Messages
    662
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Diverses et multiples

    Informations forums :
    Inscription : Mai 2008
    Messages : 662
    Par défaut
    À l’exception de types de base (int, str, float, …), et plus généralement de tous les types immutable (cela inclue les tupples, par ex.), tous les objets pythons sont toujours copiés/passés par référence (si on veut une «*vraie*» copie, il faut utiliser deepcopy).

    Tout cela est géré par un compteur de référence –*faire une copie incrémente de 1 le compteur de l’objet, supprimer une copie le décrémente de 1. Donc, del se contente de décrémenter ce compteur. La réelle suppression de l’objet n’a lieu que quand ce compteur atteint 0, et c’est seulement à ce moment que l’optionnelle __delete__() est appelée…

  3. #3
    Membre éclairé
    Profil pro
    Inscrit en
    Avril 2004
    Messages
    574
    Détails du profil
    Informations personnelles :
    Âge : 41
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations forums :
    Inscription : Avril 2004
    Messages : 574
    Par défaut
    Compris.

    Donc peut être que le soucis bien de la gestion avec Tkinter.
    J'ajoute un élément dans mon grid. par elem.grid(....)

    Juste avant de supprimer mon élément je fais :
    elem.grid_forget()
    del elem

    Mais je ne rentre pas dans ma fonction __del__

    Peut être que Tkinter conserve encore des références...
    Comment être sur qu'il n'en ait plus ?

  4. #4
    Membre Expert Avatar de PauseKawa
    Homme Profil pro
    Technicien Help Desk, maintenance, réseau, système et +
    Inscrit en
    Juin 2006
    Messages
    2 725
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Technicien Help Desk, maintenance, réseau, système et +
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juin 2006
    Messages : 2 725
    Par défaut
    Bonjour,

    La méthode pour détruire un Widget c'est .destroy()
    Les méthodes *forget des gestionnaires de géométrie ne fonts que 'retirer' le Widget de la géométrie.
    del(elem) ne fait que détruire la référence pour Python (comprendre la supprimer du namespace).
    Un Widget n'a pas besoin d’être nommer pour exister pour l'interpréteur Tcl/Tk.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    root = Tk()
    Button(root, command=root.quit, text='Quit').pack()
    root.mainloop()
    @+

  5. #5
    Membre éclairé
    Profil pro
    Inscrit en
    Avril 2004
    Messages
    574
    Détails du profil
    Informations personnelles :
    Âge : 41
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations forums :
    Inscription : Avril 2004
    Messages : 574
    Par défaut
    Désolée, mais ce n'est pas très clair...

    Cela signifie que si j'ai une liste d'élément tels que : Tkinter.Label, Tkinter.Button, etc... et que je veux les supprimer pour toujours, il faudrait que je fasse :

    label = Tkinter.Label(frame, text="pouet")
    label.grid(row=1, column=2)

    ...

    label.grid_forget()
    label.destroy()

    ??

    Oui, merci, je viens de tester c'est bien ça !
    Il faut label.destroy()
    et del list[indiceDuLabel]

    Merci beaucoup pour votre aide !

  6. #6
    Membre Expert Avatar de PauseKawa
    Homme Profil pro
    Technicien Help Desk, maintenance, réseau, système et +
    Inscrit en
    Juin 2006
    Messages
    2 725
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Technicien Help Desk, maintenance, réseau, système et +
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juin 2006
    Messages : 2 725
    Par défaut
    La surcharge n'est sans doute pas la plus mauvaise idée (Comprendre mettre le destroy() dans __del__()).
    Le .*_forget() est inutile.

    Une chose a connaître dans votre cas:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    >>> import Tkinter
    >>> root = Tkinter.Tk()
    >>> l1 = Tkinter.Label(root, text='Label1')
    >>> l1.pack()
    >>> Tkinter.Label(root, text='Label2').pack()
    >>> def lab_destroy():
    ...     for Widget in root.winfo_children(): # <<<<<<<
    ...         if isinstance(Widget, Tkinter.Label):
    ...             Widget.destroy()
    ... 
    >>> Tkinter.Button(root, text='Destroy', command=lab_destroy).pack()
    >>> Tkinter.Button(root, text='Root Destroy', command=root.destroy).pack()
    >>> root.mainloop()
    C'est ce qui se passe avec .destroy(), lorsque un 'Widget' est .destroy() ses enfants aussi. A vous de trouver la 'boite' (plagia, je sais) qui vas bien dans votre géométrie. Frame est bien sur le conteneur par excellence.

  7. #7
    Expert éminent
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 716
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 716
    Par défaut
    Salut,

    En fait __del__ n'est appelé qu'à la destruction de l'objet, çàd via le garbage collector lorsqu'il n'y plus de références.
    Lorsqu'on écrit:

    label = Tkinter.Label(frame, text="pouet")

    on crée une référence (label) à une instance de Label mais derrière l'appel, la bibliothèque tk ajoute le label dans la liste des .children de "frame".
    "del label" décrémentera le nombre de références à l'instance mais sans le .destroy la référence dans .children restera
    Exemple:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    >>> import Tkinter as tk
    >>> r = tk.Tk()
    >>> class Label(tk.Label):
    ...     def __del__(self): print 'foo'
    ...
    >>> l = Label(r, text='xxx')
    >>> l.pack()
    >>> l.destroy()  # une référence en moins...
    >>> del l           # une autre référence...
    foo                   # maintenant çà passe dans __del__
    Tout çà pour dire que la lecture de:
    Chaque article est affiché à l'aide d'un label et d'une image par exemple. En fait j'ai une liste myElems dans laquelle je place tous ce que je vais devoir afficher.

    Par exemple myElems = [MyLabel, MyButtonImage, MyLabel2, MyButtonImage2...]

    Sachant que MyLabel étends par exemple la classe Tkinter.Label...
    m'incite à vous suggérer d'utiliser la "liste" interne à un widget Frame - puisqu'il s'agit de l'ensemble (voire de la hiérarchie) des objets crées dedans - lorsque vous devez "détruire" la liste, vous détruisez la frame... et la liste d'objets qu'elle contient.

    Note: la création restera label = Tkinter.Label(frame, text="pouet").

    Bon courage
    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  8. #8
    Membre Expert Avatar de PauseKawa
    Homme Profil pro
    Technicien Help Desk, maintenance, réseau, système et +
    Inscrit en
    Juin 2006
    Messages
    2 725
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Technicien Help Desk, maintenance, réseau, système et +
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juin 2006
    Messages : 2 725
    Par défaut
    C'est bien plus clair comme cela wiztricks, merci.

  9. #9
    Expert éminent
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 716
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 716
    Par défaut
    Salut,

    Citation Envoyé par PauseKawa Voir le message
    C'est bien plus clair comme cela wiztricks, merci.
    J'espère!

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  10. #10
    Membre Expert Avatar de PauseKawa
    Homme Profil pro
    Technicien Help Desk, maintenance, réseau, système et +
    Inscrit en
    Juin 2006
    Messages
    2 725
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Technicien Help Desk, maintenance, réseau, système et +
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juin 2006
    Messages : 2 725
    Par défaut
    Maintenant l'idée de liste est bonne aussi:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    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
     
    import Tkinter as tk
     
    flist = []
    llist = []
     
    r = tk.Tk()
     
    def w_destroy(l):
        for e in l:
            e.destroy()
     
    f1 = tk.Frame(r)
    flist.append(f1)
    tk.Label(f1, text='Label f1').pack()
    f1.grid(row=0, column=0)
     
    f2 = tk.Frame(r)
    l2 = tk.Label(f2, text='Label f2')
    l2.pack()
    llist.append(l2)
    f2.grid(row=0, column=1)
     
    f3 = tk.Frame(r)
    flist.append(f3)
    l3 = tk.Label(f3, text='Label f3')
    l3.pack()
    llist.append(l3)
    f3.grid(row=1, column=0)
     
    f4 = tk.Frame(r)
    tk.Label(f4, text='Label f4').pack()
    f4.grid(row=1, column=1)
     
    tk.Button(r, text='Frame destroy', command=lambda l=flist: w_destroy(l)).grid(row=2, column=0)
    tk.Button(r, text='Label destroy', command=lambda l=llist: w_destroy(l)).grid(row=2, column=1)
     
    r.mainloop()
    Comme dit plus haut il suffit de trouver la 'liste' (boite)

  11. #11
    Membre éclairé
    Profil pro
    Inscrit en
    Avril 2004
    Messages
    574
    Détails du profil
    Informations personnelles :
    Âge : 41
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations forums :
    Inscription : Avril 2004
    Messages : 574
    Par défaut
    Je vous remercie encore pour toutes ses explications. Cela m'aide bien à comprendre comment Tkinter fonctionne.

    J'ai finalement mis ma liste directement dans le frame.
    Ensuite, je ne supprime pas mon frame à chaque fois, uniquement ce qu'il contient. Du coup, cette fois ci, je fais bien un destroy et un del.

    Merci

  12. #12
    Expert éminent
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 716
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 716
    Par défaut
    Salut,

    Je ne pense pas que Tk offre une méthode pour détruire les children "seuls" (sans la boîte qui les contient). mais on pourrait sous classer Frame pour lui ajouter un destroy_children genre:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    class Frame(tk.Frame):
         def destroy_children(self):
               for c in list(self.children.values()): c.destroy()
    Ceci dit une Frame est justement une boîte à contenir des widgets: détruire la boîte ou les widgets qui sont dedans est quasi équivalent.

    Ensuite, je ne supprime pas mon frame à chaque fois, uniquement ce qu'il contient. Du coup, cette fois ci, je fais bien un destroy et un del.
    Chaque fois que vous aller modifier le code, il faudra penser à insérer le widget dans la liste et gérer cette liste "en plus"...
    Pourquoi pas... mais c'est une source de bugs.

    Je n'aime pas les "del" et surtout ne pas avoir a en oublier un...
    Et comme j'aime bien les "boîtes" à côté de la boîte à widget, il y a la boîte à variables "locales" que sont fonctions et méthodes.

    Lorsqu'on écrit:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    def myfunction(root):
         frame = Frame(root)
         label = Label(frame,...)
    Les instances de Frame et de Label créées survivront à la sortie de la fonction alors que les variables frame et label qui contiendront des références à ces instances "survivront".

    Nous pourrions construire un contexte lib permettant une écriture style:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    def myfunction(root):
         with Frame(root) as frame:
               label = Label(frame,...)
               ....
    L'exit du context effectuant le .destroy de la frame...

    Un peu plus pédestre que le context lib mais qui revient au même:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    def myfunction(root):
         try:
               frame = Frame(root)
               label = Label(frame,...)
               ....
         finally:
               frame.destroy()
    Le propos étant d'utiliser ce que Python vous offre déjà pour gérer de façon transparente la destruction des objets devenus inutiles.

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  13. #13
    Membre Expert Avatar de PauseKawa
    Homme Profil pro
    Technicien Help Desk, maintenance, réseau, système et +
    Inscrit en
    Juin 2006
    Messages
    2 725
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Technicien Help Desk, maintenance, réseau, système et +
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juin 2006
    Messages : 2 725
    Par défaut
    Bonjour,

    Quelques petites notes:

    Dans le cadre de destroy_children la conversion en liste est inutile, un simple
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    for c in self.children.values():
        c.destroy()
    ou encore
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    for w in self.winfo_children():
        w.destroy()
    (C'est ce que j'utilise) et largement suffisant.
    De toute manière destroy s'occupe de la descendance.

    classe BaseWidget
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
        def destroy(self):
            """Destroy this and all descendants widgets."""
            for c in self.children.values(): c.destroy()
            self.tk.call('destroy', self._w)
            if self.master.children.has_key(self._name):
                del self.master.children[self._name]
            Misc.destroy(self)
    Plutôt que de gérer deux listes (votre liste et winfo_children) autant utiliser l'existante. Comprendre faire le del dans destroy_children avant le destroy du Widget. Une recherche de l'instance dans les namespaces* et le tour est jouer. Pas de considération sur a=b ici puisque l'on détruit l'objet juste après.
    * Voir juste le Global dans le cadre d'une fonction.

    Y a t'il une si grosse différence de géométrie que vous ne puissiez utiliser configure() (réutiliser vos Widgets) ? Même si vous passez de un à dix labels il suffit d' "afficher" que ce qui vous est nécessaire (via le géométry manager (pack, grid, place) > votre classe "Agenceur").

    Bien que cela ne concerne ici que la destruction du conteneur j'aime bien les constructions 'on exit' de wiztricks

    @+

    Edit: Vous pouvez utiliser votre classe classe "Agenceur" en utilisant un dico et destroy_children. A la création des Widgets vous les stockez dans un dico nom_python: instance et dans destroy_children vous faite un del du nom_python dans Globals() un destroy du Widget et pour finir vous supprimez l'item du dico.

  14. #14
    Membre Expert Avatar de PauseKawa
    Homme Profil pro
    Technicien Help Desk, maintenance, réseau, système et +
    Inscrit en
    Juin 2006
    Messages
    2 725
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Technicien Help Desk, maintenance, réseau, système et +
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juin 2006
    Messages : 2 725
    Par défaut
    Mais, j'y pense... Puisque vous utilisez une classe pour positionner vos Widgets à quel niveau les créez vous ? Comprendre que dans le cadre de locales les considérations sur del sont inutiles et dans le cadre de self cela est reviens à mon premier edit.

    @++

  15. #15
    Expert éminent
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 716
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 716
    Par défaut
    Salut,
    Dans le cadre de destroy_children la conversion en liste est inutile, un simple
    Je suis d'accord! En fait c'est un cut&paste du code 3.2:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
        def destroy(self):
            """Destroy this and all descendants widgets. This will
            end the application of this Tcl interpreter."""
            for c in list(self.children.values()): c.destroy()
            self.tk.call('destroy', self._w)
            Misc.destroy(self)
            global _default_root
            if _support_default_root and _default_root is self:
                _default_root = None
    Et "self.children.values()" est dans ce cas un memoryview. Je n'ai pas encore pris le temps de comprendre les implications de cela qui imposerait de passer par un list.... Mais, il n'est peut être pas là pour rien.

    Pour le passage par .winfo_children, le code (3.2) est:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
        def winfo_children(self):
            """Return a list of all widgets which are children of this widget."""
            result = []
            for child in self.tk.splitlist(
                self.tk.call('winfo', 'children', self._w)):
                try:
                    # Tcl sometimes returns extra windows, e.g. for
                    # menus; those need to be skipped
                    result.append(self._nametowidget(child))
                except KeyError:
                    pass
            return result
    Si le but de l'opération est de faire un w.destroy de l'arborescence des children... La méthode précédente me semble moins coûteuse... non?

    Comprendre faire le del dans destroy_children avant le destroy du Widget. Une recherche de l'instance dans les namespaces* et le tour est jouer
    Ah oui, je me souviens de vos posts voulant récupérer la variable associée à une instance. N'est-il pas plus simple de créer ces variables dans un namespace local à une fonction? instance de classe?

    A priori le globals d'un module a la durée de vie du module qui est "singleton" associé à l'application. Les variables de ces espaces sont quasi "statiques" la seule fonction qui permet de les détruire sans trop d'efforts est la fonction exit() qui termine l'application.

    fonction et instances de classes permettent d'avoir des boîtes (objets) d'une durée de vie inférieure et offrent des mécanismes de création/destruction d'espace de noms - on peut les simuler pour les variables "globales" mais bof!

    Bien que cela ne concerne ici que la destruction du conteneur j'aime bien les constructions 'on exit' de wiztricks
    Merci.
    Mon propos n'était que d'illustrer le concept de "boîte" dont je parlais sur frame et children dans un contexte "différent" de la POO.

    La "boîte" fonction est beaucoup plus "primitive" et à priori (enfin à mon sens) plus "simple" à comprendre que classes et instances de classes.

    Bon courage
    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  16. #16
    Membre Expert Avatar de PauseKawa
    Homme Profil pro
    Technicien Help Desk, maintenance, réseau, système et +
    Inscrit en
    Juin 2006
    Messages
    2 725
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Technicien Help Desk, maintenance, réseau, système et +
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juin 2006
    Messages : 2 725
    Par défaut
    Bonjour,

    Citation Envoyé par wiztricks Voir le message
    N'est-il pas plus simple de créer ces variables dans un namespace local à une fonction? instance de classe?
    C'est bien le sens de ma question de savoir ou sont créés les Widgets (durée de vie des variable).

    Citation Envoyé par wiztricks Voir le message
    Ah oui, je me souviens de vos posts voulant récupérer la variable associée à une instance.
    Loin de moi cette idée (ps: Bien que j'avais préciser que le sujet en question était expérimental je pense qu'il vas me suivre un moment).
    Pour faire 'basique' et sans se soucier de la portée des variables (comprendre globals()):
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    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
    #!/usr/bin/env python
    # -*- coding: UTF-8 -*-
    #
    # Puisque wiztricks parle de Python 3.2...
    try:
        import Tkinter as Tk
    except:
        import tkinter as Tk
     
    widgets_list = []
    pack_kwargs = {'padx': 5, 'pady': 5, 'expand': 1, 'fill': Tk.BOTH}
     
    def delwidgets():
        while len(widgets_list) > 0:
            w = widgets_list.pop()
            del globals()[w[1]]
            w[0].destroy()
            affiche()
     
    def affiche():
        t.delete(0.0, Tk.END)
        t.insert(0.0, 'globals():\n')
        t.insert(Tk.END, str(globals())+'\n\n')
        t.insert(Tk.END, 'widgets_list:\n')
        t.insert(Tk.END, str(widgets_list))
     
    if __name__ == "__main__":
        r = Tk.Tk()
        r.title('test del')
        t = Tk.Text(r)
        t.pack(pack_kwargs)
        f = Tk.Frame(r)
        l1 = Tk.Label(r, text='xxxx1')
        l1.pack(pack_kwargs)
        widgets_list.append((l1, 'l1'))
        l2 = Tk.Label(r, text='xxxx2')
        l2.pack(pack_kwargs)
        widgets_list.append((l2, 'l2'))
        f.pack()
        Tk.Button(r, text="Destroy", command=delwidgets).pack(pack_kwargs)
        Tk.Button(r, text="Quit", command=quit).pack(pack_kwargs)
        affiche()
        r.mainloop()
        r.destroy()
    Pour ce qui est de list j'avoue ne pas avoir regarder le code de tkinter sous Python 3.x (elle est sortie la 3.2 ? ). Pour moi children.values() retourne toujours une liste mais cela voudrais dire qu'il est possible que tk.call retourne autre chose (None ?). A vérifier.

    Ceci dit nommer un Widget ne sert qu'a avoir une référence pour pouvoir utiliser ses méthodes avec Python (Dans l'exemple ci-dessus les Widgets Button n'existent pas pour Python mais ils sont bien là pour l'interpréteur Tcl). Une fois les méthodes utilisées nous n'en avons plus besoin. Cela veux dire qu'une fois que les command, bind et autres protocol sont en place et si nous n'avons pas besoin du nom (Pour un Widget Text ou un Canvas par exemple) autant utiliser effectivement une fonction, voir une instance.

    @+

  17. #17
    Expert éminent
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 716
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 716
    Par défaut
    Salut,

    Loin de moi cette idée (ps: Bien que j'avais préciser que le sujet en question était expérimental je pense qu'il vas me suivre un moment).
    Bah j'espère bien que non...
    J'ai pris çà en exemple pour essayer de comprendre en remettant un "contexte" dans la discussion qui n'est pas forcément "adapté", désolé.

    Ceci dit nommer un Widget ne sert qu'a avoir une référence pour pouvoir utiliser ses méthodes avec Python (Dans l'exemple ci-dessus les Widgets Button n'existent pas pour Python mais ils sont bien là pour l'interpréteur Tcl).
    Ce n'est pas ma lecture du code qui est dans "Python-3.2/Lib/tkinter/__init__.py".
    En fait le côté Python est ce que j'appelle un "Proxy" qui traduit l'appel de méthode en commandes à passer à l'interpréteur TCL.
    Pour que çà "fonctionne", il faut bien créer une association entre l'instance Python et la chose TCL qui est derrière.

    => On peut toujours récupérer la liste des .children côté Python (et c'est ce qui fait que le del de l'instance ne fait pas passer le "reference counter" à zéro permetttant d'activer __del__).

    Une fois les méthodes utilisées nous n'en avons plus besoin. Cela veux dire qu'une fois que les command, bind et autres protocol sont en place et si nous n'avons pas besoin du nom (Pour un Widget Text ou un Canvas par exemple) autant utiliser effectivement une fonction, voir une instance.
    Le nom est ici à comprendre comme variable Python qui soit une référence à l'instance du widget crée qui permettra "plus tard" de passer des commandes spécifiques au widget - il faut bien qu'on dise "à qui"...

    Cette référence est d'autant moins nécessaire que le widget n'en a pas besoin pour survivre au contexte qui en a demandé la création.

    Note: c'est ce qui nous ramène à la discussion initiale: pourquoi del ne marche pas... et l'obligation d'avoir à passer par .destroy du widget ou d'un des parents.

    Mais Tk vient avec son espace de nommage "à lui" pouvant permettre (si nécessaire) de donner des noms à ses widgets sous la forme de chaînes de caractères (str).
    Genre: Label(frame, text='...', name='MyLabel',...).pack()...

    Ca fait quoi? Ben le .children dont les .values sont les widgets ayant même 'père' ("master" dans le contexte Tk et pourquoi "master" plutôt que "parent" est une autre discussion ).... est un dict Python qui a une clé associe l'instance "widget".
    Par défaut, cette clé est construite à partir de l'id (adresse de l'instance) convertie en str, c'est ce défaut qu'on change via name=...

    La suite de ces id est "concaténée" avec des '.' pour avoir un "nom" absolu.
    Note: la mécanique est similaire aux noms de fichiers...
    Et c'est ce nom absolu qui est utilisé pour dialoguer avec Tk via les commandes TCL.

    La doc Tkinter 8.4 reference: a GUI for Python raconte ceci:
    The term window describes a rectangular area on the desktop.

    * A top-level or root window is a window that has an independent existence under the window manager. It is decorated with the window manager's decorations, and can be moved and resized independently. Your application can use any number of top-level windows.
    * The term “window” also applies to any widget that is part of a top-level window.

    Tkinter names all these windows using a hierarchical window path name.

    * The root window's name is ".".
    * Child windows have names of the form ".n", where n is some integer in string form. For example, a window named ".135932060" is a child of the root window (".").
    * Child windows within child windows have names of the form "p.n" where p is the name of the parent window and n is some integer. For example, a window named ".135932060.137304468" has parent window ".135932060", so it is a grandchild of the root window.
    * The relative name of a window is the part past the last "." in the path name. To continue the previous example, the grandchild window has a relative name "137304468".

    The path name for any widget w can be determined by calling str(w).

    See also Section 25, “Universal widget methods” for methods you can use to operate on window names, especially the .winfo_name, .winfo_parent, and .winfo_pathname methods.
    En première lecture, pour récupérer une référence vers l'instance du label crée via: Label(frame, text='...', name='MyLabel',...).pack()... on pourrait faire:

    1. on récupère le nom associé à frame via frame.winfo_name,
    2. nom + '.MyLabel' pour avoir le nom absolu du label,
    3. on récupère le retour de .nametowidget(nom + '.MyLabel')

    Pas de soucis, çà marche.... Mais c'est un peu "fatigue".

    Juste faire: frame.children['MyLabel'] est quand même plus court, non?

    Mais on peut garder le nametowidget pour des cas plus compliqués.
    Imaginons une "toolbar", une "menubar" scotchés à une root.
    A partir de n'importe quel widget issu de root, w.nametowidget('.toolbar') retournera la 'boîte' correspondante.

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  18. #18
    Membre Expert Avatar de PauseKawa
    Homme Profil pro
    Technicien Help Desk, maintenance, réseau, système et +
    Inscrit en
    Juin 2006
    Messages
    2 725
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Technicien Help Desk, maintenance, réseau, système et +
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juin 2006
    Messages : 2 725
    Par défaut
    Bonjour,

    Enfin

    Citation Envoyé par wiztricks Voir le message
    Ce n'est pas ma lecture du code qui est dans "Python-3.2/Lib/tkinter/__init__.py".
    En fait le côté Python est ce que j'appelle un "Proxy" qui traduit l'appel de méthode en commandes à passer à l'interpréteur TCL.
    Pour que çà "fonctionne", il faut bien créer une association entre l'instance Python et la chose TCL qui est derrière.
    Et bien en fait ce n'est pas un proxy mais son cousin proche, un wrapper.

    /usr/lib/python3.2/tkinter/__init__.py
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    """Wrapper functions for Tcl/Tk.
    Et c'est bien le problème initial de zuzuu : Il donne accès à Tk qui n'est pas du Python > Les 'objets' Tk ne sont pas des 'objets' Python et il donne une 'interface' pour les manipuler.
    Voici se qui se passe pour zuzuu :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    >>> import Tkinter as Tk
    >>> root = Tk.Tk()
    >>> l = Tk.Label()
    >>> l = Tk.Label()
    >>> l = Tk.Label()
    >>> globals()
    {'__builtins__': <module '__builtin__' (built-in)>, 'l': <Tkinter.Label instance at 0xb746134c>, '__package__': None, 'Tk': <module 'Tkinter' from '/usr/lib/python2.6/lib-tk/Tkinter.pyc'>, '__name__': '__main__', 'root': <Tkinter.Tk instance at 0xb77039ac>, '__doc__': None}
    >>> del l
    >>> root.winfo_children()
    [<Tkinter.Label instance at 0xb77062cc>, <Tkinter.Label instance at 0xb74612ac>, <Tkinter.Label instance at 0xb746134c>]
    >>> globals()
    {'__builtins__': <module '__builtin__' (built-in)>, '__package__': None, 'Tk': <module 'Tkinter' from '/usr/lib/python2.6/lib-tk/Tkinter.pyc'>, '__name__': '__main__', 'root': <Tkinter.Tk instance at 0xb77039ac>, '__doc__': None}
    Citation Envoyé par wiztricks Voir le message
    En première lecture, pour récupérer une référence vers l'instance du label crée via: Label(frame, text='...', name='MyLabel',...).pack()...
    Dois je faire remarquer que vous ne 'nommez' pas le Widget pour Python

    Citation Envoyé par wiztricks Voir le message
    Juste faire: frame.children['MyLabel'] est quand même plus court, non?
    Voilà la réponse pour zuzuu
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    >>> import Tkinter as Tk
    >>> root = Tk.Tk()
    >>> label_list = ['l1', 'l2', 'l3']
    >>> for l in label_list:
    ...     Tk.Label(root, text=l, name=l).pack()
    ... 
    >>> globals()
    {'label_list': ['l1', 'l2', 'l3'], '__builtins__': <module '__builtin__' (built-in)>, 'l': 'l3', '__package__': None, 'Tk': <module 'Tkinter' from '/usr/lib/python2.6/lib-tk/Tkinter.pyc'>, '__name__': '__main__', 'root': <Tkinter.Tk instance at 0xb77249ac>, '__doc__': None}
    >>> # Pas de 'pollution' du namespace
    ... 
    >>> root.winfo_children()
    [<Tkinter.Label instance at 0xb74822ac>, <Tkinter.Label instance at 0xb74823cc>, <Tkinter.Label instance at 0xb748246c>]
    >>> # Les Labels sont bien présents
    ... 
    >>> for l in label_list:
    ...     root.children[l].destroy()
    ... 
    >>> root.winfo_children()
    []
    >>> # ;p
    ... 
    >>> del label_list[:]
    >>> globals()
    {'label_list': [], '__builtins__': <module '__builtin__' (built-in)>, 'l': 'l3', '__package__': None, 'Tk': <module 'Tkinter' from '/usr/lib/python2.6/lib-tk/Tkinter.pyc'>, '__name__': '__main__', 'root': <Tkinter.Tk instance at 0xb77249ac>, '__doc__': None}
    Sauf de vouloir rester sur le Hello, World de l'__init__.py en question nous devons donc nous intéresser à ce qui se trouve derrière la couverture.

    @+

    Edit: Rajout du del

  19. #19
    Expert éminent
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 716
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 716
    Par défaut
    Salut,
    Nous disons globalement la même chose.
    Mais quand je lis:
    Et c'est bien le problème initial de zuzuu : Il donne accès à Tk qui n'est pas du Python > Les 'objets' Tk ne sont pas des 'objets' Python et il donne une 'interface' pour les manipuler.
    Impossible de ne pas réagir:
    L'interface crée des objets Python associés aux objets TCL associés aux widgets (ceux qu'on voit à l'écran).

    Créons des widgets/objets Tk qui ne soient pas des objets Python:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    >>> import Tkinter as tk
    >>> r = tk.Tk()
    >>> r.tk.call('frame', '.frame')
    '.frame'
    >>> r.tk.call('pack', '.frame')
    ''
    >>> r.tk.call('label', '.frame.mylabel', '-text', 'foo')
    '.frame.mylabel'
    >>> r.tk.call('pack', '.frame.mylabel')
    ''
    Les objets Python n'existent pas:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    >>> r.winfo_children()
    []
    >>>
    Impossible de les manipuler mais les objets Tk sont là...
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    >>> r.tk.call('winfo', 'children', '.')
    ('.frame',)
    >>>
    Cela dit... Je ne comprends plus trop de quoi on parle en disant "nommer"...
    Faisons (presque) la même chose que précédemment via Tkinter:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    >>> import Tkinter as tk
    >>> r = tk.Tk()
    >>> f = tk.Frame()
    >>> f.pack()
    >>> l = tk.Label(f, text='foo', name='label')
    >>> l.pack()
    r, f, et l sont des variables globales qui ajoutent une référence non pas à l'objet Tk (cà c'est le boulot de Tkinter) mais à l'objet Python correspondant (crée par Tkinter).

    Le "nom" du widget est la chaîne de caractère identifiant le widget qui permettra à Tkinter et TCL/TK de savoir à quoi appliquer la méthode x, y ou z...
    Quels sont ces NOMS?
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    >>> f._w
    '.36192544'
    >>> f._name
    '36192544'
    >>> l._w
    '.36192544.label'
    >>> l._name
    'label'
    >>>
    Après suivant l'interface qu'on utilise, les palabres pour en changer l'état ou lui appliquer une méthode diffèrent suivant la représentation...
    Si nous voulons changer le "text" de "label", pourra se faire:
    >>> l.configure(text='bar')
    ou
    >>> r.tk.call ('.36192544.label', 'configure', '-text', 'bar')

    L'objet est le même. Son nom est: '.36192544.label'.
    Et je conviens que '.36192544.label' n'est pas très heureux!!!
    Reprenons... sans variable "locales" et des noms plus conviviaux...
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    >>> import Tkinter as tk
    >>> r = tk.Tk()
    >>> tk.Frame(name='frame').pack()
    >>> tk.Label(r.children['frame'], text='foo', name='label').pack()
    Pour changer le texte du label, i.e changer l'état de... nous n'avons plus la variable locale (qui aide bien) mais nous avons pris la précaution de lui donner un nom plus utilisable celui que Tkinter attribue par défaut.

    Autrement dit r.tk.call('.frame.label', 'configure', '-text', 'bar') mais nous pouvons aussi passer par nametowidget "en absolu":
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    >>> label = r.nametowidget('.frame.label')
    >>> label.configure(text='bar')
    ou par les .children en relatif.

    En forçant un peu le trait, nous pourrions écrire des choses comme:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    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
    46
    47
    48
    import Tkinter as tk
     
    cmdsWidget = '.cmdsWidget'
    frameWidget = '.frameWidget'
     
    def create_widgets():
        frame = nametowidget(frameWidget)
        for x in range(3):
            text = 'label-%d' % x
            tk.Label(frame, text=text, name=text).pack()
        toggle_buttons()
     
    def destroy_widgets():
        frame = nametowidget(frameWidget)
        for name in frame.children.keys():
            if name.startswith('label'):
                frame.children[name].destroy()
        toggle_buttons()
     
    def toggle_buttons():
        frame = nametowidget(cmdsWidget)
        for name in 'b_create', 'b_destroy':
            b = frame.children[name]
            state = b.configure('state')[-1] # get last item
            if state == 'normal':
                b.configure(state='disabled')
            elif state == 'disabled':
                b.configure(state='normal')
            else:
                raise Exception('fatal: not supposed ot get there ;-(, state="%s"' % state)
     
    def create_commands():
        frame = nametowidget(cmdsWidget)
        tk.Button(frame, text="Create", name="b_create",command=create_widgets).pack(side=tk.LEFT)
        tk.Button(frame, text="Destroy", name="b_destroy", 
            command=destroy_widgets, state='disabled').pack(side=tk.LEFT)
        tk.Button(frame, text="Quit", command=exit).pack(side=tk.LEFT)
     
    def nametowidget(name):
        root = tk._default_root
        return root.nametowidget(name)
     
    if __name__ == '__main__':
        tk.Tk()
        tk.Frame(height=10, relief=tk.SUNKEN, name=cmdsWidget[1:]).pack(side=tk.TOP)
        tk.Frame(height=100, bd=2, relief=tk.SUNKEN, name=frameWidget[1:]).pack(fill=tk.BOTH)
        create_commands()
        tk.mainloop()
    L'objet de l'exemple est de montrer quelques constructions dans lesquelles on utilise le "nommage" des widgets et l'arborescence construite derrière par TCL/Tk et l'interface Python.

    J'ai forcé le trait pour que les fonctions récupèrent les instances à partir de leurs "noms": pas de variable "globale", pas même pour root.

    Chaque fonction s'occupe de son rectangle (frame) bien défini et ignore le reste (excepté le nom des fonctions ajoutés aux "Button"s).

    Mais intuitivement, inutile de proposer "Create" si on ne peut faire que "Destroy". La fonction "toggle_buttons" modifie donc conditionnellement l'état des boutons... Rien de bien méchant... sauf les questions qu'on peut se poser sur la résolution des diverses dépendances.

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  20. #20
    Membre Expert Avatar de PauseKawa
    Homme Profil pro
    Technicien Help Desk, maintenance, réseau, système et +
    Inscrit en
    Juin 2006
    Messages
    2 725
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Technicien Help Desk, maintenance, réseau, système et +
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juin 2006
    Messages : 2 725
    Par défaut
    Bonsoir,

    Citation Envoyé par wiztricks Voir le message
    Mais quand je lis:

    Impossible de ne pas réagir:
    L'interface crée des objets Python associés aux objets TCL associés aux widgets (ceux qu'on voit à l'écran).
    Nous somme bien d'accord, il existe une référence Python. Cela ne fonctionnerais pas sinon.
    Je rajouterais juste que le 'nom' (widget._name) s'il n'est pas spécifié (name=) correspond à repr(id(widget)) (Et widget._w = '.' + widget._name si le master est '.' (le root Tk) ou master._w + '.' + widget._name).
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    >>> import Tkinter as Tk
    >>> root = Tk.Tk()
    >>> l = Tk.Label(root)
    >>> print l
    .3066113676L
    >>> repr(id(l))
    '3066113676L'
    >>> print l._w
    .3066113676L
    Dans le cadre ou nous avons Tk.Label(root) celui ci est stocké dans le Widget (self._name > widget._name).
    On a fais le tour je pense

    Je pense que zuzuu auras maintenant bien compris que son del ne détruisait que des références mais que derrière il accumulais les objets. Qu'importe que le garbage collector passe par là.
    Dans sont cas le plus simple est donc, comme vous le proposez, d'utiliser name=

    @+

+ Répondre à la discussion
Cette discussion est résolue.
Page 1 sur 2 12 DernièreDernière

Discussions similaires

  1. [MCD] Problème de compréhension du fonctionnement d'un héritage.
    Par Maverick57 dans le forum Schéma
    Réponses: 3
    Dernier message: 13/01/2012, 09h22
  2. [AJAX] Compréhension du fonctionnement d'ajax
    Par kikou732 dans le forum AJAX
    Réponses: 1
    Dernier message: 18/12/2011, 09h32
  3. [Portlet] [Fonctionnement] Problème de compréhension
    Par Bichette12 dans le forum Portails
    Réponses: 3
    Dernier message: 17/07/2009, 14h48
  4. Réponses: 1
    Dernier message: 31/08/2007, 09h23
  5. [C#] Problème de compréhension du fonctionnement d'un Timer
    Par cyllix dans le forum Windows Forms
    Réponses: 2
    Dernier message: 26/07/2006, 17h58

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo