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 :

scope des sous fonction et TkInter


Sujet :

Python

  1. #1
    Membre habitué Avatar de sopsag
    Profil pro
    Inscrit en
    Octobre 2008
    Messages
    224
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Octobre 2008
    Messages : 224
    Points : 190
    Points
    190
    Par défaut scope des sous fonction et TkInter
    Bonjour,

    je suis confronté à un problème métaphysique de portée de variable...
    Voici un petit test qui illustre mon propos :
    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
    def ti_test ():
     
        import Tkinter
     
        dialog = Tkinter.Tk()
        dialog.grid()
        button = Tkinter.Button( dialog,text='OK', command=dialog.quit )
        button.grid()
     
        i = 123 # 'i' est bien défini au même niveau que 'button'
     
        def spam ():
            print 'spam'
            button['text'] = button['text'].swapcase()
     
        def eggs ():
            print 'eggs ',i
            i += 1
     
        dialog.bind('a',lambda e : spam())
        dialog.bind('z',lambda e : eggs())
        dialog.mainloop()
        dialog.destroy()
     
    ti_test()
    Quand j'appuie sur 'a' je vois bien mon 'OK' devenir 'ok' et vice versa. Donc, la variable 'button' est visible depuis 'spam'.
    Mais quand j'appuis sur 'z', j'ai un exception qui me dit que 'i' n'est pas défini dans le contexte de 'eggs' !

    Alors, pourquoi ces 2 poids et 2 mesures ?

    Pendant que j'y suis : comment on "bind" la barre d'espace ?
    [WinXP sp3 / Visual 2005 / Eclipse Ganymede / Python 2.6]
    Hadrien

  2. #2
    Membre expérimenté
    Homme Profil pro
    Inscrit en
    Avril 2004
    Messages
    1 048
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Avril 2004
    Messages : 1 048
    Points : 1 378
    Points
    1 378
    Par défaut
    i est une variable locale à ti_test().
    button est une instance.

    de même que :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    i = 3
     
    def toto():
         print i ===> erreur
     
    print i ==> 3

  3. #3
    Membre habitué Avatar de sopsag
    Profil pro
    Inscrit en
    Octobre 2008
    Messages
    224
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Octobre 2008
    Messages : 224
    Points : 190
    Points
    190
    Par défaut

    En effet !
    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
    class ham:
        def __init__(self):
            self.x = 456
     
    def ti_test ():
     
        import Tkinter
     
        dialog = Tkinter.Tk()
        button = Tkinter.Button( dialog,text='OK', command=dialog.quit )
        button.grid()
     
        i = 123
        h = ham()
     
        def spam ():
            print 'spam ',h.x
            h.x += 1
     
        def eggs ():
            print 'eggs ',i
            i += 1
     
        dialog.bind('a',lambda e : spam())
        dialog.bind('z',lambda e : eggs())
        dialog.mainloop()
        dialog.destroy()
     
    ti_test()
    spam fonctionne alors que eggs lève une exception !
    Mais c'est ultra moche !!!
    Ça veut dire que que i n'est pas un objet ? Je croyais qu'en python tout était objet...
    Déçu déçu déçu !

    En tous cas, merci du tuyau.

    Et heu... le bind de la barre d'espace ?
    [WinXP sp3 / Visual 2005 / Eclipse Ganymede / Python 2.6]
    Hadrien

  4. #4
    Membre habitué Avatar de sopsag
    Profil pro
    Inscrit en
    Octobre 2008
    Messages
    224
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Octobre 2008
    Messages : 224
    Points : 190
    Points
    190
    Par défaut
    Voici une version minimale qui met en évidence le phénomène :
    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
    class ham:
        pass
     
    def spam ():
     
        h = ham()
        h.x = 456
        i   = 123
     
        def eggs ():
            print 'h.x : ',h.x
            h.x += 1
            try:
                print 'i : ',i
                i += 1
            except Exception,e:
                print '---->',e
     
        eggs()
        eggs()
        eggs()
    donne ça :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    h.x :  456
    i :  ----> local variable 'i' referenced before assignment
    h.x :  457
    i :  ----> local variable 'i' referenced before assignment
    h.x :  458
    i :  ----> local variable 'i' referenced before assignment
    [WinXP sp3 / Visual 2005 / Eclipse Ganymede / Python 2.6]
    Hadrien

  5. #5
    Membre expérimenté
    Homme Profil pro
    Inscrit en
    Avril 2004
    Messages
    1 048
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Avril 2004
    Messages : 1 048
    Points : 1 378
    Points
    1 378
    Par défaut
    soit tu utilises global, soit tu crées une class pour tes variables.
    perso, je fais :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    class Data:
        "je mes ici toutes les variables dont j'ai besoin globalement"
        i = 0
        ma_var_1 = 'hello'
     
    def coucou():
        print Data.ma_var_1

  6. #6
    Membre habitué Avatar de sopsag
    Profil pro
    Inscrit en
    Octobre 2008
    Messages
    224
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Octobre 2008
    Messages : 224
    Points : 190
    Points
    190
    Par défaut
    Oui mais non.
    Je ne veux pas de variables globales (beark) !
    Je veux juste que les sous fonctions (ie fonctions filles définies à l'intérieur d'une fonction mère) voient les locales de la fonction mère.
    Typiquement
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    def ti_test ():
        i = 5
        def spam ():
            print i
    ne marche pas !
    [WinXP sp3 / Visual 2005 / Eclipse Ganymede / Python 2.6]
    Hadrien

  7. #7
    Membre expérimenté
    Homme Profil pro
    Inscrit en
    Avril 2004
    Messages
    1 048
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Avril 2004
    Messages : 1 048
    Points : 1 378
    Points
    1 378
    Par défaut
    passes-les en argument.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    def ti_test ():
        i = 5
        def spam (i):
            print i

  8. #8
    Membre extrêmement actif
    Profil pro
    Inscrit en
    Janvier 2007
    Messages
    1 418
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2007
    Messages : 1 418
    Points : 1 658
    Points
    1 658
    Par défaut
    La raison est subtile.
    Pour bien comprendre, il faut partir des notions fondamentales et remonter progressivement jusqu’à l’observation.

    Accrochez vos ceintures.





    FONCTION f()

    Dans la fonction f() suivante, A n’est qu’utilisée pour fournir une valeur dans un calcul.


    Si A existe comme variable globale définie à l’extérieur de f(), elle peut être utilisée au sein de f().
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    def f():
        B = 95 + A
        print B
        print locals(),'\n'
    A = 405
    f()
    500
    {'B': 500}
    Au passage, on remarque que B est crée comme une variable locale de f().



    Si A n’existe pas en tant que variable globale, il y a erreur.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    def f():
        B = 95 + A
        print B
        print locals(),'\n'
    f()
    Traceback (most recent call last):
    File "E:\Python\Essais Python\zzzz TTTT.py", line 6, in <module>
    f()
    File "E:\Python\Essais Python\zzzz TTTT.py", line 3, in f
    B = 95 + A
    NameError: global name 'A' is not defined
    Pour le moment, rien de sidérant.





    FONCTION g()

    Dans la fonction g() suivante , A subit cette fois une assignation.
    Cette assignation fait de A une variable locale qui rend sans utilité la valeur de la variable extérieure A.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    def g():
        A = 105
        B = 95 + A
        print B
        print locals(),'\n'
    A = 405
    g()
    200
    {'A': 105, 'B': 200}
    L’assignation A = 105 consiste en deux choses:
    - création d’un objet integer 105 dans la mémoire vive de l'ordinateur
    - binding (attachement, association) du nom A à l’objet integer 105 créé. De façon imagée, on parle de collage de l’étiquette A sur l’objet.
    Assignment statements are used to (re)bind names to values and to modify attributes or items of mutable objects
    http://docs.python.org/reference/simple_stmts.html

    Concrètement, ce binding est réalisé par l’ajout de A à l’espace de noms de la fonction, c'est à dire par inscription du nom A dans le dictionnaire qui implémente l’espace de noms de la fonction:
    A namespace is a mapping from names to objects. Most namespaces are currently implemented as Python dictionaries, but that’s normally not noticeable in any way (except for performance), and it may change in the future.
    http://www.python.org/doc/2.6.2/tuto...ut-terminology

    Une variable est donc dite “bound“ quand elle est présente dans un dictionnaire-espace de noms. Mais quel espace de noms ? Espace de noms relatif à quoi ?
    Il faut chercher dans les docs de quoi il s'agit. C'est un peu un jeu de pistes qui n'est pas des plus clairs.

    Tout espace de noms est relatif à un bloc.
    Un block , c’est
    a piece of Python program text that is executed as a unit.
    http://docs.python.org/reference/exe...ng-and-binding

    On tire l’info qu’un espace de noms est relatif à un bloc
    de ceci:
    Each occurrence of a name in the program text refers to the binding of that name established in the innermost function block containing the use.
    (tout début de la page)
    et ceci
    If a name is bound in a block, it is a local variable of that block. If a name is bound at the module level, it is a global variable. (The variables of the module code block are local and global.) If a variable is used in a code block but not defined there, it is a free variable.
    dans
    http://www.python.org/doc/2.6.2/refe...ng-and-binding

    Il est question de «name established» et non pas de «name defined» parce qu’un même nom peut se trouver “établi“ dans un bloc donné, c’est à dire utilisable dans ce bloc, sans y avoir été défini formellement. L’étendue des blocs dans lesquels un nom est utilisable à partir du bloc dans lequel il a été défini s’appelle la portée (scope). En effet:
    If the definition occurs in a function block, the scope extends to any blocks contained within the defining one, unless a contained block introduces a different binding for the name.
    http://docs.python.org/reference/exe...ng-and-binding
    Ceci décrit que l’utilisation d’un nom NAME qui a été défini dans un bloc fonction donné peut être faite jusqu’à l’intérieur d’un «contained block» (c’est le cas de A dans la fonction f) , À MOINS QUE le «contained block» définisse un autre attachement du nom NAME (c’est le cas de A dans la fonction g puisque ce sont les assignations qui définissent un binding, cf plus haut).

    Cet autre attachement est un attachement au dictionnaire-espace de noms du «contained block» et il définit NAME comme variable locale dans le «contained block». En effet:
    Each occurrence of a name in the program text refers to the binding of that name established in the innermost function block containing the use.
    http://www.python.org/doc/2.6.2/refe...ng-and-binding
    Bien sûr, si on veut qu’une assignation dans un «contained block» ne provoque pas un nouveau binding, on doit mettre le statement global NAME.



    Pour en revenir à la fonction g,
    il n’y a pas de statement global A dans le bloc g qui signifierait “toute modification de A affecte la variable globale A qui existe à l’extérieur de cette fonction“.
    L’assignation A = 105 fait donc de A une variable locale au même titre que B.
    La valeur de A locale est utilisée dans la foulée dans un calcul affectant B.
    Mais c’est l’assignation qui prime sur l’utilisation pour décréter que A est une variable locale.





    FONCTION h()

    Dans la fonction h(), la subtilité apparait sous forme d’une valse d’étiquette.
    Il n’y a plus que la seule variable A, subissant d’abord une assignation, puis une ré-assignation.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    def h():
        A = 5
        A = 95 + A
        print A
        print locals(),'\n'
    A = 405
    h()
    100
    {'A': 100}
    L’assignation A = 5 définit A comme variable locale de la même façon que dans la précédente fonction.
    Puis l’instruction A = 95 + A utilise la valeur de A pour un calcul dont le résultat affecte A elle même. C’est à dire que A = 95 + A n’est pas une assignation, c’est simplement une réassignation: A est réassignée à un nouvel objet integer 200 = l’étiquette A est déplacée d’un objet à une autre.





    FONCTION p()

    Dans la fonction p() , cela devient plus subtil.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    def p():
        print locals()
        A = 95 + A
        print A
        print locals(),'\n'
    A = 405
    p()
    ll y a une seule instruction A = 95 + A.
    Comme il n’y a pas de statement global A et pas d’assignation préalable comme dans la fonction h() , la valeur de A à l'extérieur de la fonction n'a pas d'importance et d'utilité, et l'instruction A = 95 + A doit provoquer une assignation d’objet à la variable locale A, c’est à dire une création de l’étiquette A sur un objet nouvellement créé puisqu’en l’absence de valeur préalable de A, l’étiquette A n’existe pas à l’entrée dans la fonction.

    Le problème qui survient, c’est que l’objet qui doit être assigné à la variable A doit être calculé par l’expression 95 + A dans laquelle A n’a pas de valeur au moment où doit être effectué le calcul.

    Une erreur se produit donc.
    {}
    Traceback (most recent call last):
    File "E:\Python\Essais Python\zzzz TTTT.py", line 5, in <module>
    p()
    File "E:\Python\Essais Python\zzzz TTTT.py", line 2, in p
    A = 95 + A
    UnboundLocalError: local variable 'A' referenced before assignment
    Le message d’erreur signale:
    « UnboundLocalError: local variable 'A' referenced before assignment »
    L’erreur est logiquement qualifiée de UnboundLocalError puisqu’une assignation crée un binding comme on l’a vu: mais le programme ne trouve pas l’assignation qui devrait avoir précédé l’utilisation de A dans le calcul puisque le fait que la variable A doit être une variable locale l’empêche d’aller chercher en dehors de la fonction.



    ----------------------------------------



    Il y a une couche supplémentaire de subtilité.

    Reprenons les fonctions g() et h() qui ont réussi à créer A en tant que variable locale et à l’utiliser dans une expression de calcul.
    Et inversons l’ordre des instructions.

    FONCTION ginv()

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    def ginv():
        print locals()
        B = 95 + A
        A = 105
        print B
        print locals(),'\n'
    A = 405
    ginv()
    FONCTION hinv()

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    def hinv():
        print locals()
        A = 95 + A
        A = 5
        print A
        print locals(),'\n'
    A = 405
    hinv()
    Pour chacune, on obtient l’erreur:
    UnboundLocalError: local variable 'A' referenced before assignment



    • Cette erreur est aisément compréhensible pour hinv()

    car la situation est identique pour hinv() à celle de la fonction p() :
    l’instruction A = 95 + A doit être exécutée avant que vienne l’assignation A = 5; donc A = 95 + A représente une assignation qui provoque la catégorisation de A comme variable locale sans que l’expression 95 + A puisse être évaluée, à cause de l’absence de valeur pour A au départ.



    • Pour ce qui est de ginv() , il faut faire un petit peu attention pour remarquer que l’expression 95 + A est rencontrée avant que l’assignation A = 105 soit rencontrée , et alors que B = 95 + A n’est pas une assignation concernant A.


    Dans ces conditions, pourquoi y a-t-il erreur
    UnboundLocalError: local variable 'A' referenced before assignment
    comme si le programme cherchait à créer une variable locale ?

    Il paraîtrait en effet tout à fait normal que la valeur de A en tant que variable globale soit utilisée au niveau de cette instruction, avant qu’à la ligne suivante sa valeur soit changée et qu’elle devienne variable locale puisqu’il n’y a pas de statement global A.

    Mais la doc assure que ça ne se passe pas comme ça:
    If a name binding operation occurs anywhere within a code block, all uses of the name within the block are treated as references to the current block. This can lead to errors when a name is used within a block before it is bound. This rule is subtle. Python lacks declarations and allows name binding operations to occur anywhere within a code block.
    http://www.python.org/doc/2.6.2/refe...ng-and-binding

    Donc une variable donnée ne peut pas être locale (= se référer à l’«innermost block») et globale (= se référer à un «enclosing block» ) à la fois.
    Et idée importante à retenir aussi: une opération d’attachement de nom de variable peut se produire n’importe où dans un bloc.



    -------------------------------------



    Les codes suivants montrent un peu plus ce qu’il se passe.

    FONCTION utilise_A_ext()
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    def utilise_A_ext():
        print 'locals()',locals()
        loc = 202 + A_ext
        print 'locals()',locals()
    A_ext = 123
    utilise_A_ext()
    locals() {}
    locals() {'loc': 325}
    La variable loc n’est pas encore une variable locale à l’entrée du programme dans la fonction.
    À la sortie, loc est devenue une telle variable locale.
    La définition de loc en tant que variable locale n’intervient qu’au sein de la fonction au moment de l’assignation loc = 202 + A_ext, comme on l’a vu plus haut.



    Ensuite, il faut bien croire qu’au moment d’exécuter cette instruction loc = 202 + A_ext, le programme fait une revue générale de toutes les variables intervenant dans le bloc fonction: cela se voit grâce à la fonction suivante.


    FONCTION modifie_A_ext()
    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
    def modifie_A_ext():
        print 'locals()',locals()
     
        try:
            loc = 66 + A_ext
            try:
                A_ext = 2000
            except:
                print 'je ne peux pas faire ca a vext !'
        except:
            print 'je ne peux pas executer cette instruction !'
     
        print 'locals()',locals()
     
    A_ext = 123
    modifie_A_ext()
    locals() {}
    je ne peux pas executer cette instruction !
    locals() {}
    On constate que l’erreur survient à la première instruction dans laquelle apparait A_ext c’est à dire loc = 66 + A_ext et non pas sur l’instruction suivante A_ext = 2000.

    Cette idée a priori bizarre (le programme examine l’ensemble du bloc avant toute exécution précise) est heureusement confortée par la doc, bien que ce ne soit pas d'une absolue clarté:
    Python lacks declarations and allows name binding operations to occur anywhere within a code block. The local variables of a code block can be determined by scanning the entire text of the block for name binding operations.
    http://www.python.org/doc/2.6.2/refe...ng-and-binding

    Ceci signale qu’une instruction d’assignation qui déclenche un binding peut se trouver n’importe où dans un bloc: il n’y a pas de déclaration en tête de fonction comme dans d’autres langages.
    Corrélativement, les variables locales d’un bloc de code sont déterminées par un scanning du texte entier du bloc.




    -------------------------------------



    On arrive enfin au bout du décorticage.

    C’est la présence de l’assignation A_ext = 2000 qui détermine que A doit être liée comme variable locale à la fonction modifie_A_ext() , et c'est à cause de cette assignation postérieure qu’il y a donc un problème avant que cette instruction soit rencontrée, du fait que le programme examine préalablement l’ensemble du bloc de code, il anticipe.

    La présence d’une instruction A_ext = 2000 + A_ext produit la même conséquence en compliquant simplement un peu plus la perception de l’enchaînement des causes: elle constitue à la fois l’assignation responsable de la catégorisation de A_ext en variable locale et une expression qui représente une utilisation de A_ext sans disposer de la valeur de A_ext.
    Sous cette dernière forme, on retrouve l’apect de la fonction eggs()

    Enfin la voilà !!
    Dans la fonction eggs() :
    l’instruction qui utilise la variable i est print i (au lieu d’une utilisation dans une expression de calcul) , tandis que i += 1 est bien une instruction qui à la fois utilise la valeur de [G]i [/G]et est une assignation qui doit provoquer la définition de i comme variable locale.



    ------------------------



    Pour résumer (parce que je sens que tout le monde n’aura pas tout lu...), le problème apparaît particulièrement sybillin parce que la fonction
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    def eggs ():
        print 'eggs ',i
        i += 1
    regroupe 3 subtilités:

    • i += 1 constitue une assignation qui définit un binding de i comme variable locale

    • une opération de binding peut être placée n’importe où dans un bloc.

    • l’examen de l’ensemble des noms de variables présents dans le bloc fonction est fait par scanning par le programme en amont de toute opération sur une variable dans la fonction


    .

  9. #9
    Membre extrêmement actif
    Profil pro
    Inscrit en
    Janvier 2007
    Messages
    1 418
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2007
    Messages : 1 418
    Points : 1 658
    Points
    1 658
    Par défaut
    Le message précédent a épluché les raisons qui font que ton code ne marche pas.

    Celui-ci indique ce que j'ai trouvé pour le faire marcher.



    Il y a deux types de mesures qui peuvent être prises.


    Le principe est d’empècher que l’instruction i+=1 fasse de i une variable locale à eggs() , parce que ce caractère local est contradictoire avec le fait que la valeur initiale de i est définie à l’extérieur de la fonction eggs() , ce qui pose problème pour faire print i et pour évaluer la nouvelle valeur i+1 que doit prendre i (je ne fais que radoter).


    1)
    On peut empêcher ceci en faisant de i une variable globale.
    Mais qui dit globale, dit globale; c’est à dire que i ne peut pas ètre définie globale dans la fonction ti_test () mais seulement dans le __main__ .

    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
    def ti_test ():
     
        import Tkinter
     
        dialog = Tkinter.Tk()
        dialog.grid()
        button = Tkinter.Button( dialog,text='OK', command=dialog.quit )
        button.grid()
     
        def spam ():
            print 'spam'
            button['text'] = button['text'].swapcase()
     
        def eggs ():
            global i
            print 'eggs ',i
            i += 1
     
        dialog.bind('a',lambda e : spam())
        dialog.bind('z',lambda e : eggs())
        dialog.mainloop()
        dialog.destroy()
     
    i = 123
    ti_test()


    2)
    Le facteur critique qui cause le problème est que i est un objet non mutable parce que cela détermine l’instruction i+=1 à être une instruction de ré-assignation = modification du binding du nom i , c’est à dire en fait modification d’un espace de nom.
    Et si on ne met pas un statement global i, cet espace de nom est celui de la fonction.

    On peut s’en sortir en faisant de i un objet mutable: de ce fait, l'instruction i+=1 ne nécessitera pas la création d’un autre objet sur lequel déplacer l’étiquette i , mais elle pourra être effectuée en modifiant simplement sur place la portion de mémoire réservée à l’objet i.

    En effet
    Assignment statements are used to (re)bind names to values and to modify attributes or items of mutable objects
    http://docs.python.org/reference/simple_stmts.html

    Ce qui donne deux solutions, dont je suis le premier surpris à avoir résussi à les trouver:

    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
    def ti_test ():
     
        import Tkinter
     
        dialog = Tkinter.Tk()
        dialog.grid()
        button = Tkinter.Button( dialog,text='OK', command=dialog.quit )
        button.grid()
     
        dialog.i = 123
     
        def spam ():
            print 'spam'
            button['text'] = button['text'].swapcase()
     
        def eggs ():
            print 'eggs ',dialog.i
            dialog.i += 1
     
        dialog.bind('a',lambda e : spam())
        dialog.bind('z',lambda e : eggs())
        dialog.mainloop()
        dialog.destroy()
     
    ti_test()


    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
    def ti_test ():
     
        import Tkinter
     
        dialog = Tkinter.Tk()
        dialog.grid()
        button = Tkinter.Button( dialog,text='OK', command=dialog.quit )
        button.grid()
     
        i = [123]
     
        def spam ():
            print 'spam'
            button['text'] = button['text'].swapcase()
     
        def eggs ():
            print 'eggs ',i[0]
            i[0] += 1
     
        dialog.bind('a',lambda e : spam())
        dialog.bind('z',lambda e : eggs())
        dialog.mainloop()
        dialog.destroy()
     
    ti_test()



    Je pense que la solution dialog.i qui fait de i un attribut de l’instance dialog est préférable puisque la fonction eggs() est appelée, si je comprends bien les choses, à partir de cette instance.
    Les processus ont donc l’attribut “sous la main“, si ça veut dire quelque chose.


    ----------------------------------


    Voilà, ça a été long et pas mal théorique, mais comme l'a écrit quelqu'un récemment
    « sans ça on se retrouve vite à faire n'importe quoi en OO »

    .

  10. #10
    Membre éprouvé
    Homme Profil pro
    Inscrit en
    Décembre 2007
    Messages
    758
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : France

    Informations professionnelles :
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Décembre 2007
    Messages : 758
    Points : 970
    Points
    970
    Par défaut
    bonjour,

    je te propose encore une approche différente.

    de mon point de vue, donner un attribut i à "dialog" ça reste de la bidouille.
    je suis pas un fin connaisseur de Tkinter, mais il me semble qu'une approche basée sur les classes est plus propre:

    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
    import Tkinter
     
    class Ti_test(object):
     
        def __init__(self,i=0):
     
            self.dialog = Tkinter.Tk()
            self.dialog.grid()
            self.button = Tkinter.Button(self.dialog,text="OK",command=self.dialog.quit)
            self.button.grid()
     
            self.dialog.bind('a',lambda e : self.spam())
            self.dialog.bind('z',lambda e : self.eggs())
     
            self.i = i
     
        def spam(self):
     
            print 'spam'
            self.button['text'] = self.button['text'].swapcase()
     
        def eggs(self):
     
            print 'eggs',self.i
            self.i += 1
     
        def __call__(self):
     
            self.dialog.mainloop()
            self.dialog.destroy()
     
     
    ti_test = Ti_test(123)
    res = ti_test()
    print "resultat = ",res

  11. #11
    Membre habitué Avatar de sopsag
    Profil pro
    Inscrit en
    Octobre 2008
    Messages
    224
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Octobre 2008
    Messages : 224
    Points : 190
    Points
    190
    Par défaut
    Wow !
    Quel magnifique cours de python

    Toutefois, il reste une question, qui me parait plus mystérieuse encore...
    Dans ce code
    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
    def spam ():
     
        class ham:
            pass
     
        h = ham()
        h.x = 456
        i   = 123
     
        def eggs ():
            h.x += 1
            i += 1
     
        eggs()
     
    spam()
    "h.x += 1" se passe bien alors que
    "i += 1" lève une exception !
    Qu'y a-t-il de différent entre h.x et i ?
    Ce sont des instances de la classe int tous les 2 mais un est membre d'une classe et pas l'autre...
    [WinXP sp3 / Visual 2005 / Eclipse Ganymede / Python 2.6]
    Hadrien

  12. #12
    Membre éprouvé
    Avatar de Antoine_935
    Profil pro
    Développeur web/mobile
    Inscrit en
    Juillet 2006
    Messages
    883
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur web/mobile

    Informations forums :
    Inscription : Juillet 2006
    Messages : 883
    Points : 1 066
    Points
    1 066
    Par défaut
    Citation Envoyé par sopsag Voir le message
    h.x += 1" se passe bien alors que
    "i += 1" lève une exception !
    Qu'y a-t-il de différent entre h.x et i ?
    Ce sont des instances de la classe int tous les 2 mais un est membre d'une classe et pas l'autre...
    La réponse à ta question a été donnée par Eyquem:
    Le facteur critique qui cause le problème est que i est un objet non mutable parce que cela détermine l’instruction i+=1 à être une instruction de ré-assignation = modification du binding du nom i , c’est à dire en fait modification d’un espace de nom.
    Et si on ne met pas un statement global i, cet espace de nom est celui de la fonction.
    Kango se rapproche d'une solution propre. Le fait est que programmer avec des fermetures (closures, bref une fonction dans une fonction) est plus ou moins réservé à certains buts, tels l'intelligence artificielle.

    La pensée orientée objet voudrait que tu crées une classe, qui hérite de Tkinter.Tk et implémente les méthodes nécessaires.
    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
    >>> from Tkinter import *
    >>> 
    >>> class Dialog(Tk):
    ...     def __init__(self):
    ...         Tk.__init__(self)
    ...
    ...         self.__button = b = Button(self, text="Click me",
    ...                                    command=self.__click)
    ...         b.pack()
    ...         self.__i = 123
    ...
    ...     def __click(self):
    ...         print self.__i
    ...         self.__i += 1
    ...
    >>> d = Dialog()
    >>> 123
    124
    125
    126
    127
    128
    129
    130
    131

  13. #13
    Membre habitué Avatar de sopsag
    Profil pro
    Inscrit en
    Octobre 2008
    Messages
    224
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Octobre 2008
    Messages : 224
    Points : 190
    Points
    190
    Par défaut
    Merci de ta réponse !
    2 choses :

    1 - je suis tout à fait d'accord : ce morceau de code n'est pas très propre mais je le poste uniquement pour illustrer le problème que je cherche à comprendre.
    En fait je cherche à cerner un des mécanismes interne de python plutôt qu'un design de code...

    2 - je ne comprends toujours pas pourquoi le comportement est différent entre i et h.x...
    Ils sont déclarés au même endroit et modifiés de la même façon.
    De plus, l'emploi du mot clef global n'est pas une solution car (si j'ai bien compris) il permet d'accéder à l'espace de nom global mais pas à celui de la fonction englobante.

    Je rappelle que dans mon problème, j'essaye d'accéder à des variables déclarées dans la fonction spam à partir de la fonction eggs qui est elle-même une sous-fonction de spam.
    Ça marche pour une instance de classe h mais pas pour une variable "simple" i.
    [WinXP sp3 / Visual 2005 / Eclipse Ganymede / Python 2.6]
    Hadrien

  14. #14
    Membre actif
    Profil pro
    Inscrit en
    Avril 2008
    Messages
    159
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2008
    Messages : 159
    Points : 224
    Points
    224
    Par défaut
    Citation Envoyé par sopsag Voir le message
    2 - je ne comprends toujours pas pourquoi le comportement est différent entre i et h.x...
    Ils sont déclarés au même endroit et modifiés de la même façon.
    De plus, l'emploi du mot clef global n'est pas une solution car (si j'ai bien compris) il permet d'accéder à l'espace de nom global mais pas à celui de la fonction englobante.
    Ça pourtant été dit plusieurs fois : la subtilité tient aux éléments mutables et non mutables.
    Si i est non mutable (un entier par exemple), lorsque tu fais
    Il n'est pas réellement modifié. En fait, c'est une copie de i qui est utilisée. Il y a donc réaffectation d'une nouvelle variable i, et ceci dans la portée de ta fonction. i perd donc son ancienne portée.
    Si i est une liste, ou une instance de classe, ou tout autre objet mutable, ton
    ne fait aucune affectation. Il modifie juste la valeur de i. i conserve donc la portée qu'il avait avant.

    Du reste, c'est ce qu'à dit eyquem plus haut. Que je remercie parce que j'étais resté un peu perplexe devant cet exemple, et son intervention m'a éclairé.

  15. #15
    Membre habitué Avatar de sopsag
    Profil pro
    Inscrit en
    Octobre 2008
    Messages
    224
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Octobre 2008
    Messages : 224
    Points : 190
    Points
    190
    Par défaut
    Encore merci, mais j'avais bien compris tout ça...

    Je vais reformuler ma question avec les termes de ta réponse :

    Pourquoi est-ce qu'une instance de classe (h) est mutable alors qu'un entier (i) ne l'est pas ?

    Je croyait qu'en python tout était objet et que du coup, un entier était aussi une instance de classe...
    [WinXP sp3 / Visual 2005 / Eclipse Ganymede / Python 2.6]
    Hadrien

Discussions similaires

  1. Empecher "l'ouverture" des sous fonctions d'un fichier-m
    Par lapinoufly dans le forum MATLAB
    Réponses: 1
    Dernier message: 01/07/2009, 10h07
  2. affectation de fonction dans des sous-menus
    Par chossette9 dans le forum Interfaces Graphiques
    Réponses: 33
    Dernier message: 27/05/2009, 11h29
  3. Recherche des sous-fonctions
    Par Laugeek dans le forum Développement
    Réponses: 3
    Dernier message: 16/04/2009, 12h15
  4. Appel de fonction dans des sous dossiers
    Par milach dans le forum MATLAB
    Réponses: 1
    Dernier message: 03/01/2008, 14h24
  5. Propriétés des sous-fonctions
    Par rodb7 dans le forum C
    Réponses: 21
    Dernier message: 06/03/2006, 09h34

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