Récupérer une variable d'une fonction lancée par un menu

Résolu

Bonjour,

Un code simplifié pour mettre en évidence le problème:

from tkinter import *

class Point_3D:
    "Definition d'un point 3D"
    def __init__(self,x = 0.0,y = 0.0,z=0.0):
        self.__x = x
        self.__y = y
        self.__z = z

    def __repr__(self):
        return "X %f Y %f Z %f"%(self.__x,self.__y,self.__z)

def new_point():
    
    pt = Point_3D(10.0, 12.0, 30.0)

def show_point(evt):
    
    graph_area.create_text(20,20,text = str(pt.x))

WIDTH, HEIGHT = 400, 400

main_win = Tk()
main_win.title('Test')
main_win.geometry(str(WIDTH)+'x'+str(HEIGHT)+'+400+100')

menu_bar = Menu(main_win)
menu_file = Menu(menu_bar, tearoff=0)
menu_bar.add_cascade(label="Nouveau", underline=0, menu=menu_file)
menu_file.add_command(label="Dossier", command=new_point)
main_win.config(menu=menu_bar)

graph_area = Canvas(main_win,bg='white',height=380,width=380)
graph_area.place(x = 10,y = 10)
graph_area.bind("<Button-1>", show_point)

main_win.mainloop()

Comment récupérer pt dans show_point sachant que cette instance

est créé dans une fonction lancée par un menu

(et sans variables globales)

Merci d'avance pour vos réponses
Windows / Edge 120.0.0.0

10 réponses

  1. Salut.

    Pourquoi ne pas utiliser un classe de façon à partager tes valeurs ?

    from tkinter import *
    
    class Point_3D:
        "Definition d'un point 3D"
        def __init__(self,x = 0.0,y = 0.0,z=0.0):
            self.__x = x
            self.__y = y
            self.__z = z
    
        def __repr__(self):
            return "X %f Y %f Z %f"%(self.__x,self.__y,self.__z)
    
        x = property(lambda s: s.__x)
        y = property(lambda s: s.__y)
        z = property(lambda s: s.__z)
    
    
    class Point:
        def __init__(self, area):
            self.area = area
            self.pt = Point_3D(10.0, 12.0, 30.0)
    
        def add(self, *args):
            pass
    
    
        def show(self, evt=None):
            self.area.create_text(20, 20,text=self.pt.x)
    
    WIDTH, HEIGHT = 400, 400
    
    main_win = Tk()
    main_win.title('Test')
    main_win.geometry(str(WIDTH)+'x'+str(HEIGHT)+'+400+100')
    
    graph_area = Canvas(main_win,bg='white',height=380,width=380)
    graph_area.place(x = 10,y = 10)
    
    
    pt = Point(graph_area)
    
    menu_bar = Menu(main_win)
    menu_file = Menu(menu_bar, tearoff=0)
    menu_bar.add_cascade(label="Nouveau", underline=0, menu=menu_file)
    menu_file.add_command(label="Dossier", command=pt.add)
    main_win.config(menu=menu_bar)
    
    graph_area.bind("<Button-1>", pt.show)
    
    main_win.mainloop()

    Le code est à revoir, mais l'idée globale est là.

    0
    1. Merci pour cette réponse

      mais pt est créé en ligne 40 en dehors de toute fonction

      Moi, je dois le créer dans une fonction dès l'appui sur le menu

      pas au lancement du programme

      0
  2. Bah il suffit de mettre la création dans la méthode add.

    class Point:
        def __init__(self, area):
            self.area = area
            self.pt = None
    
    
        def add(self, *args):
            self.pt = Point_3D(10.0, 12.0, 30.0)
    
    
        def show(self, evt=None):
            if self.pt is not None:
                self.area.create_text(20, 20,text=self.pt.x)

    Si ça ne va pas, faut mieux expliquer comment doit se comporter ton script.

    0
    1. Contributeur

      bonjour,

      suggestion:

      def new_point():
          
          graph_area.phil_pt = Point_3D(10.0, 12.0, 30.0)
      
      def show_point(evt):
          
          graph_area.create_text(20,20,text = str(graph_area.phil_pt.x))
      0
      1. Contributeur

        alternative:

        def new_point():
            pt = Point_3D(10.0, 12.0, 30.0)
            graph_area.bind("<Button-1>", lambda e : show_point(e,pt))
           
        def show_point(evt,p):
            graph_area.create_text(20,20,text = str(p.x))
        0
    2. Contributeur

      Pourquoi utilises-tu une variable globale (graph_area) et écris-tu que tu veux t'en passer?

      Sans variable globale:

      from tkinter import *
      
      class Point_3D:
          "Definition d'un point 3D"
          def __init__(self,x = 0.0,y = 0.0,z=0.0):
              self.__x = x
              self.__y = y
              self.__z = z
              self.x=x
      
          def __repr__(self):
              return "X %f Y %f Z %f"%(self.__x,self.__y,self.__z)
      
      def new_point(g):
          pt = Point_3D(10.0, 12.0, 30.0)
          g.bind("<Button-1>", lambda e : show_point(e,g,pt))
         
      def show_point(evt,gr,p):
          gr.create_text(20,20,text = str(p.x))
      
      WIDTH, HEIGHT = 400, 400
      
      main_win = Tk()
      main_win.title('Test')
      main_win.geometry(str(WIDTH)+'x'+str(HEIGHT)+'+400+100')
      menu_bar = Menu(main_win)
      menu_file = Menu(menu_bar, tearoff=0)
      menu_bar.add_cascade(label="Nouveau", underline=0, menu=menu_file)
      main_win.config(menu=menu_bar)
      graph_area = Canvas(main_win,bg='white',height=380,width=380)
      graph_area.place(x = 10,y = 10)
      menu_file.add_command(label="Dossier", command=lambda  : new_point(graph_area))
      
      main_win.mainloop()
      
      0
      1. Bonjour,

        J'ai trop simplifié mon exemple, je vais donc reformuler ma demande

        from tkinter import *
        
        class Object():
        
            def __init__(self,param_1, param_2):
                self.__param_1 = param_1
                self.__param_2 = param_2
        
        def new_object(obj_type):
            global obj
        
            pos1 = obj_type.index('x')
            p1, p2 = float(obj_type[:pos1]), float(obj_type[pos1+1:])
        
            obj = Object(p1, p2)
        
        objects_types = ['170x80', '100x50']
        WIDTH, HEIGHT = 400, 400
        
        main_win = Tk()
        main_win.title('Test')
        main_win.geometry(str(WIDTH)+'x'+str(HEIGHT)+'+400+100')
        
        menu_bar = Menu(main_win)
        menu_file = Menu(menu_bar, tearoff=0)
        menu_bar.add_cascade(label="Nouveau", underline=0, menu=menu_file)
        for k in range(len(objects_types)): menu_file.add_command(label='Objet '+str(k), command = lambda obj = objects_types[k] : new_object(obj)) 
        main_win.config(menu=menu_bar)
        
        graph_area = Canvas(main_win,bg='white',height=380,width=380)
        graph_area.place(x = 10,y = 10)
        
        main_win.mainloop()

        Je crée une instance en lançant une fonction lambda avec mon menu

        On peut lui passer des arguments mais pas récupérer de valeur de retour

        Or, je dois ensuite utiliser mon instance dans plusieurs fonctions de mon

        application réelle, j'ai donc écrit global obj dans cette fonction, mais

        je voudrais bien m'en passer

        (la fonction principale contient aussi ce global et ensuite, je passe

        l'instance en argument aux autres fonctions)

        Pour résumer:

        créer une instance suite à une action dans un menu, ou avec un bind

        sur une touche du clavier, par exemple (donc pas de return possible)

        puis utiliser cette instance dans plusieurs fonctions du code sans

        global

        0
        1. Contributeur

          L'utilisation de obj en ligne 27, c'est uniquement pour créer de la confusion?

          Peux-tu fournir un exemple plus complet, et testable?

          0
        2. Contributeur

          Un exemple, qui, sans global, fait un print, après la boucle, du dernier objet choisi.

          from tkinter import *
          
          class Object():
          
              def __init__(self,param_1, param_2):
                  self.__param_1 = param_1
                  self.__param_2 = param_2
          
          def new_object(m,obj_type):
              pos1 = obj_type.index('x')
              p1, p2 = float(obj_type[:pos1]), float(obj_type[pos1+1:])
          
              m.phil_obj = Object(p1, p2)
          
          objects_types = ['170x80', '100x50']
          WIDTH, HEIGHT = 400, 400
          
          main_win = Tk()
          main_win.title('Test')
          main_win.geometry(str(WIDTH)+'x'+str(HEIGHT)+'+400+100')
          menu_bar = Menu(main_win)
          menu_file = Menu(menu_bar, tearoff=0)
          menu_bar.add_cascade(label="Nouveau", underline=0, menu=menu_file)
          for k in range(len(objects_types)): menu_file.add_command(label='Objet '+str(k),
                  command = lambda ot = objects_types[k] : new_object(main_win,ot)) 
          main_win.config(menu=menu_bar)
          
          graph_area = Canvas(main_win,bg='white',height=380,width=380)
          graph_area.place(x = 10,y = 10)
          
          main_win.mainloop()
          print(main_win.phil_obj._Object__param_1,main_win.phil_obj._Object__param_2)
          
          0
        3. Contributeur
          @yg_be

          un autre exemple

          from tkinter import *
          
          class M():
              pass
          
          class Object():
          
              def __init__(self,param_1, param_2):
                  self.__param_1 = param_1
                  self.__param_2 = param_2
          
          def new_object(mm,obj_type):
              pos1 = obj_type.index('x')
              p1, p2 = float(obj_type[:pos1]), float(obj_type[pos1+1:])
          
              mm.obj = Object(p1, p2)
          
          m=M()
          objects_types = ['170x80', '100x50']
          WIDTH, HEIGHT = 400, 400
          
          main_win = Tk()
          main_win.title('Test')
          main_win.geometry(str(WIDTH)+'x'+str(HEIGHT)+'+400+100')
          menu_bar = Menu(main_win)
          menu_file = Menu(menu_bar, tearoff=0)
          menu_bar.add_cascade(label="Nouveau", underline=0, menu=menu_file)
          for k in range(len(objects_types)): menu_file.add_command(label='Objet '+str(k),
                  command = lambda ot = objects_types[k] : new_object(m,ot)) 
          main_win.config(menu=menu_bar)
          
          graph_area = Canvas(main_win,bg='white',height=380,width=380)
          graph_area.place(x = 10,y = 10)
          
          main_win.mainloop()
          print(m.obj._Object__param_1,m.obj._Object__param_2)
          
          0
      2. Holà yg_be

        Merci pour ça

        Dans le 1er exemple, l'objet est donc un attribut de la fenêtre principale ?

        Je pense que je vais opter pour le 2eme exemple avec la classe M() vide

        Je teste tout ça dans mon code réel ...

        0
        1. Pourquoi une classe vide ?

          Tout simplement passer en autres arguments de ta fonction anonyme, les références des fonctions ayant besoin de travailler avec tes valeurs.

          class Object:
              def __init__(self, param_1, param_2):
                  self._param_1 = param_1
                  self._param_2 = param_2
          
          
          def new_object(obj_type, call_func):
              pos1 = obj_type.index('x')
              p1, p2 = float(obj_type[:pos1]), float(obj_type[pos1+1:])
          
              call_func(Object(p1, p2))
          
          def func(obj):
              print(obj._param_1, obj._param_2)
          
          

          Et donc

          for k in range(len(objects_types)):
              menu_file.add_command(
                  label=f'Objet {k})',
                  command = lambda : new_object(objects_types[k], func),
              )
          0
          1. Non, ça ne va pas:

            en fait je ne veux pas lancer func() tout de suite

            avec mon menu, je choisi seulement un objet pour initialiser une instance

            ensuite, je remplis des Entry boxes et j'appuie sur la touche Entrée du clavier

            main_win.bind("<Return>", calculation)

            J'ai donc ceci:

            def new_object(obj_type):
            
                global obj
            
                ........

            et:

            def calculation(evt):
            
                global obj
            
                #on lance les fonctions utiles en passant obj en argument

            Donc, 2 global à éliminer ...

            0
            1. Contributeur

              Tu n'as pas expliqué pourquoi tu voulais travailler sans "global".

              "global" est souvent un indice d'un programme mal conçu.  Cependant, il ne suffit pas de supprimer "global" pour obtenir un programme bien conçu.

              La présence d'un objet attaché à rien et utilisé un peu partout, que ce soit via "global", ou pas, me semble un mauvais signe quant à la qualité de la structure des données du programme.

              0
          2. Bonjour yg_be,

            Oui, tu as surement raison, je vais prendre un peu de recul et revoir

            tout ça.

            Cependant, avant de clore cet appel, je voudrais te poser une dernière

            question

            Dans un certain nombre d'applications professionnelles, on trouve ce type

            de menu:

            Ne parlons pas de Word ou Excel, qui proposent un nom par défaut lorsque l'on

            clique Fichier -> Nouveau

            Lorsque j'étais en activité, j'utilisais un soft de CFAO, et l'on entrait

            tout de suite le nom du fichier pièce à créer, ensuite, on travaillait à modéliser

            une pièce, faire des plans, etc ..

            A la fin on cliquait Fichier -> Enregistrer (on on cliquait l'icone que l'on

            appelait familièrement "la biscotte")

            Moi en Python, je fais comme ceci:

            menu_bar = Menu(main_win)
            file_menu = Menu(menu_bar, tearoff=0)
            menu_bar.add_cascade(label="Fichier", menu=file_menu)
            file_menu.add_command(label="Nouveau", command=new_file)
            file_menu.add_command(label="Ouvrir...", command=open_file)
            file_menu.add_command(label="Sauvegarder", state = DISABLED, command=save_file)
            file_menu.add_command(label="Sauvegarder sous ...", state = DISABLED, command=save_file_as)
            file_menu.add_command(label="Fermer", state = DISABLED, command=close_file)
            file_menu.add_separator()
            file_menu.add_command(label="Quitter", command=main_win.destroy)

            J'ai donc ces 2 fonctions:

            def new_file():
                ''' Creates a new file'''
            
                current_file =  filedialog.asksaveasfilename(initialdir = os.getcwd(),title = "Nouveau fichier",filetypes =(("Fichiers modèles", "*.prt"),("Tous fichiers","*.*")))
            
            
            def save_file():
                ''' Saves the current file '''
            
                with open(current_file,'wb') as fo:
                    pickle.dump(objects_list, fo)

            Et il faut bien récupérer current_file créé dans new_file()

            Comment faisaient-ils donc, ces castars de chez Siemens, pour traiter

            ce cas-là ?

            (et je pense que dans une boite comme ça, ce n'était pas des amateurs)

            0
            1. Contributeur

              Je pense que la façon dont Siemens traitait cette situation dépendait du langage et de la modularité de leur programme.

              En Python, je ferais quelque chose du genre:

              class Univers():
                  pass
              def new_file():
                  Univers.current_file =  input("fn?")
              def save_file():
                  print(Univers.current_file)
              new_file()
              save_file()
              
              0
          3. Pas mal ...

            Pour info, le soft avait été développé initialement en C, puis retaillé en C++ au milieu

            des années 80...

            0
            1. Contributeur

              J'ai regardé le code de l'éditeur "ed", éditeur très ancien sous Unix.  Il est écrit en C, et le nom du fichier en cours est simplement enregistré via une variable déclarée hors des fonctions, donc une variable dont la portée est globale.

              Les déclarations de variables sont cependant tellement différentes en Python par rapport au C (et C++, C#), je n'en déduirais pas que c'est une bonne idée d'utiliser "global" en Python.

              Je trouve que Python est peu intuitif, et crée des confusions sources d'erreurs, en permettant implicitement d'utiliser des variables globales sans les déclarer explicitement comme telles.

              Donc je préfère, en Python, éviter toute utilisation, explicite ou implicite, de variable globale.

              0