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

Résolu
Phil_1857 Messages postés 1883 Date d'inscription lundi 23 mars 2020 Statut Membre Dernière intervention 28 février 2024 - 14 déc. 2023 à 12:22
yg_be Messages postés 22733 Date d'inscription lundi 9 juin 2008 Statut Contributeur Dernière intervention 29 avril 2024 - 16 déc. 2023 à 20:34

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

A voir également:

10 réponses

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
Phil_1857 Messages postés 1883 Date d'inscription lundi 23 mars 2020 Statut Membre Dernière intervention 28 février 2024 178
Modifié le 14 déc. 2023 à 13:15

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

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
yg_be Messages postés 22733 Date d'inscription lundi 9 juin 2008 Statut Contributeur Dernière intervention 29 avril 2024 1 477
14 déc. 2023 à 18:15

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
yg_be Messages postés 22733 Date d'inscription lundi 9 juin 2008 Statut Contributeur Dernière intervention 29 avril 2024 1 477
14 déc. 2023 à 18:29

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
yg_be Messages postés 22733 Date d'inscription lundi 9 juin 2008 Statut Contributeur Dernière intervention 29 avril 2024 1 477
14 déc. 2023 à 18:49

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

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
Phil_1857 Messages postés 1883 Date d'inscription lundi 23 mars 2020 Statut Membre Dernière intervention 28 février 2024 178
Modifié le 15 déc. 2023 à 12:08

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
yg_be Messages postés 22733 Date d'inscription lundi 9 juin 2008 Statut Contributeur Dernière intervention 29 avril 2024 1 477
15 déc. 2023 à 12:37

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
yg_be Messages postés 22733 Date d'inscription lundi 9 juin 2008 Statut Contributeur Dernière intervention 29 avril 2024 1 477
15 déc. 2023 à 13:16

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
yg_be Messages postés 22733 Date d'inscription lundi 9 juin 2008 Statut Contributeur Dernière intervention 29 avril 2024 1 477 > yg_be Messages postés 22733 Date d'inscription lundi 9 juin 2008 Statut Contributeur Dernière intervention 29 avril 2024
15 déc. 2023 à 13:22

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
Phil_1857 Messages postés 1883 Date d'inscription lundi 23 mars 2020 Statut Membre Dernière intervention 28 février 2024 178
15 déc. 2023 à 13:30

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

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
Phil_1857 Messages postés 1883 Date d'inscription lundi 23 mars 2020 Statut Membre Dernière intervention 28 février 2024 178
Modifié le 15 déc. 2023 à 14:18

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
yg_be Messages postés 22733 Date d'inscription lundi 9 juin 2008 Statut Contributeur Dernière intervention 29 avril 2024 1 477
15 déc. 2023 à 17:49

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
Phil_1857 Messages postés 1883 Date d'inscription lundi 23 mars 2020 Statut Membre Dernière intervention 28 février 2024 178
16 déc. 2023 à 11:19

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
yg_be Messages postés 22733 Date d'inscription lundi 9 juin 2008 Statut Contributeur Dernière intervention 29 avril 2024 1 477
16 déc. 2023 à 14:57

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
Phil_1857 Messages postés 1883 Date d'inscription lundi 23 mars 2020 Statut Membre Dernière intervention 28 février 2024 178
16 déc. 2023 à 15:16

Pas mal ...

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

des années 80...

0
yg_be Messages postés 22733 Date d'inscription lundi 9 juin 2008 Statut Contributeur Dernière intervention 29 avril 2024 1 477
16 déc. 2023 à 20:34

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