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

Résolu
Phil_1857 Messages postés 1883 Date d'inscription   Statut Membre Dernière intervention   -  
yg_be Messages postés 23437 Date d'inscription   Statut Contributeur Dernière intervention   -

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. quidam
     

    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. Phil_1857 Messages postés 1883 Date d'inscription   Statut Membre Dernière intervention   169
       

      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. quidam
     

    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
  3. yg_be Messages postés 23437 Date d'inscription   Statut Contributeur Dernière intervention   Ambassadeur 1 588
     

    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. yg_be Messages postés 23437 Date d'inscription   Statut Contributeur Dernière intervention   1 588
       

      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
  4. yg_be Messages postés 23437 Date d'inscription   Statut Contributeur Dernière intervention   Ambassadeur 1 588
     

    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
  5. Vous n’avez pas trouvé la réponse que vous recherchez ?

    Posez votre question
  6. Phil_1857 Messages postés 1883 Date d'inscription   Statut Membre Dernière intervention   169
     

    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. yg_be Messages postés 23437 Date d'inscription   Statut Contributeur Dernière intervention   1 588
       

      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. yg_be Messages postés 23437 Date d'inscription   Statut Contributeur Dernière intervention   1 588
       

      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
      1. yg_be Messages postés 23437 Date d'inscription   Statut Contributeur Dernière intervention   1 588 > yg_be Messages postés 23437 Date d'inscription   Statut Contributeur Dernière intervention  
         

        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
  7. Phil_1857 Messages postés 1883 Date d'inscription   Statut Membre Dernière intervention   169
     

    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
  8. quidam
     

    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
  9. Phil_1857 Messages postés 1883 Date d'inscription   Statut Membre Dernière intervention   169
     

    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. yg_be Messages postés 23437 Date d'inscription   Statut Contributeur Dernière intervention   1 588
       

      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
  10. Phil_1857 Messages postés 1883 Date d'inscription   Statut Membre Dernière intervention   169
     

    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. yg_be Messages postés 23437 Date d'inscription   Statut Contributeur Dernière intervention   1 588
       

      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
  11. Phil_1857 Messages postés 1883 Date d'inscription   Statut Membre Dernière intervention   169
     

    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. yg_be Messages postés 23437 Date d'inscription   Statut Contributeur Dernière intervention   1 588
       

      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