Sending of variable captured from a form .pyw with tkinter to an independent function in a .py

1

Hello everyone I am new to python, I hope you can help me, the problem I have is the following.

I have a form (tkinter) .pyw where I collect some data and I intend to take them to an independent function that runs in parallel from a py.

I'm trying to pass the data through a function using a list but the data does not arrive ... I'll explain it better with code and I hope you can guide me on this thanks ...

in the code I have placed > > > STEPS < < < < > to show the execution flow

steps 1, 2, 4, 5 are executed in function 1 (.py file) and step 3 is executed in function 2 (.pyw file)

function 1 name file functions.py

class FunctionsClass():

#esta variable la declaro aca fuera de cualquier funcion directo al inicio de la clase con el objetivo de que sea accesible desde
#cualquier función y con la esperanza de que
# al modificar su valor con la funcion "ModNames()" los valores se mantengan en memoria pero eso no ocurre

_PDFdatos=[]

    #esta funcion "ModNames()" tine como objetivo ser llamada desde el form en tkinter y modificar "_PDFdatos"
    # con esperando que el valor se mantenga en memoria >>> LO QUE NO OCURRE <<<
    def ModNames(self,PDFuserName,PDFuserLast,PDFuserNum,PDFfileType):
        #limpueza de variales
        self._PDFdatos.clear()

        #>>>PASO 4<<< quepa destacar que si coloco un print aca me
        # muestra los datos que estoy enviando desde la app desde la
        # funcion return_entry() 
        self._PDFdatos=[PDFuserName, PDFuserLast, PDFuserNum, PDFfileType]
        return self._PDFdatos

    def GETdatos(self):
        return self._PDFdatos

    def GetName(self):
        #las variables abajo cargan direcciones desde un archivo
        _Instdir=sysVar.VarInstalationDir()
        _dirPDF=sysVar.VarDirPDF()
        ldir="{}{}".format(_Instdir,"/AppGetName")
        os.chdir(ldir)
        #ojo aca llamo al formulario tkinter para cargar los datos
        comando="{}".format("\"python RootForm.pyw\"")
        #>>>PASO 2<<< se ejecuta la app grafica
        os.system(comando)
        os.chdir(_dirPDF)

    def CambiarNombrePDF(self,_dirPDF,_convert2PDF)
        #>>>PASO 1<<< se llama a la funcion GetName()   

        self.GetName()

        # >>>PASO 5<<< LUEGO en este punto donde supongo debería tener
        # los valores en _PDFdatos  dado que uso self.GETdatos() y me deberia
        # retornar la misma lista que acabo de modificar pero no funciona
        # ojo escribi la funcion self.GETdatos() como para probar una 
        # alternativa ya que en un principio simplemente utilice 
        # _PDFuserName=self._PDFdatos[0] y tampoco funciono

        _PDFdatos=self.GETdatos()

        _PDFuserName=_PDFdatos[0]
        _PDFuserLast=_PDFdatos[1]
        _PDFuserNum=_PDFdatos[2]
        _PDFfileType=_PDFdatos[3]

function 2 file named RootForm.pyw

this app is called from the "GetName ()" function in the functions.py file and additionally imports the functions.py in order to use the ModNames () function to return the values:

from functions import *
Job=FunctionsClass()

class AppGUI(Frame):
    def __init__(self, master=None):
        Frame.__init__(self, master)
        self.grid()

        #funcion que se utiliza para devolver los datos a la funcion objetivo
        #llamando a ModNames() que fue declarada en FunctionsClass.py

        def return_entry():
            PDFuserName=_PDFuserName.get()
            PDFuserLast=_PDFuserLast.get()
            PDFuserNum=_PDFuserNum.get()
            PDFfileType=_PDFfileType.get()

            #>>>PASO 3<<< SE ENVIAN LOS DATOS 
            Job.ModNames(PDFuserName,PDFuserLast,PDFuserNum,PDFfileType)   
            self.master.destroy()

        Label(Frame1, text="Enter customer's Name: ").grid(row=1, column=1, sticky=W)
        _PDFuserName=StringVar()
        Entry(Frame1, textvariable = _PDFuserName).grid(row=1, column=200,  sticky=W)

        Label(Frame1, text="Enter customer's Last Name: ").grid(row=2, column=1, rowspan = 1, columnspan = 6, sticky=W)
        _PDFuserLast=StringVar()
        Entry(Frame1, textvariable = _PDFuserLast).grid(row=2, column=200, sticky=W)

        Label(Frame1, text="Enter customer's Last 4 SSN: ").grid(row=3, column=1, rowspan = 1, columnspan = 6, sticky=W)
        _PDFuserNum=StringVar()
        Entry(Frame1, textvariable = _PDFuserNum).grid(row=3, column=200, sticky=W)

        Label(Frame1, text="Enter Document Type: ").grid(row=4, column=1, rowspan = 1, columnspan = 6, sticky=W)
        _PDFfileType=StringVar()
        Entry(Frame1, textvariable = _PDFfileType).grid(row=4, column=200, sticky=W)

        Button(Frame2, text="ACCEPT",command=return_entry).grid(row=6,column=3,sticky=E+W)

        self.master.mainloop()


root = Tk()
app = AppGUI(master=root)

Actualization 1 ., I put the comment that I deleted on the recommendation of the moderators, so that other readers can follow the thread: thank you very much Abulafiabpor your interest, as I had said before, it is true what you raise from the memory and I realized when I started to check the memory I'd of the variable id (self._PDFdatos) when accessing said variable from the app to modify it and check id in effect was different from when I opened it from it's script.

When I realized this, I started to integrate both the .pyw and the .py files and now I call the graphic app directly from the original script, which theoretically prevents me from running two different processes so that they now have () it does not make any sense since they are part of the code inserted in it. I wanted to discard the writing of data on disk since this process must check the folder every 5 seconds that brings a lot of wear to the client's disk, you think Is there a way to pass the data from the graphic app to the code or my only option is to finally write them to the disk? Thanks !!

Update 2 I'm going to try what you think of the return, since the other option or answers that you suggest does not apply to me much since in fact I am capturing the data that the user enters and uses the _PDFfileType = StringVar () from the beginning and with the get () if I see the data I can even manipulate them, but only on the side of the app (given that you mention that the app class created its own instance and its own variable ...

I'll let you know how it goes ..., thanks

Update 3

Hello FJSevilla , the procedures I did not understand are:

if __name__ == "__main__": inst = FunctionsClass() inst.cambiar_nombre_pdf(None, None)

and this one:

def __init__(self, master=None): Frame.__init__(self, master)

However, reading your answer extensively, I got the link that you passed to me about the difference between attributes of instances and class attributes, so I think that's where I'm going to continue researching. once again thank you ...:)

    
asked by Yoka R 08.10.2018 в 04:12
source

2 answers

1

If I do not understand wrong, your execution flow would be:

  • Launch the file functions.py with a command of the style python functions.py (I suppose).
  • That script, by means of code that you do not show in the question, instantiates an object of the class FunctionsClass and calls its method CambiarNombrePDF() .
  • This function (via GetName() ) launches another script contained in the file RootForm.pyw . This other script has the mission to ask the user for certain data through a graphical interface.
  • You want the data entered by the user to reach the FunctionsClass object that you had instantiated in step 2. But to do this, you make a from functions import * and instances a new object of type FunctionsClass , in which you call ModNames() and you finish.
  • Again in the original object you expect that the changes that ModNames() made in the other object, during step 4, have appeared "magically" in this other object.
  • Is it correct?

    That approach can not work, for two reasons:

  • Even if you declare _PDFdatos as a class attribute (I suppose with the hope that it is shared by all objects instantiated from that class), when you modify it with self._PDFdatos , a new object attribute will be created, instead of changing the value to the attribute of the class. While you only read it, you will be reading the one in the class (the same for all instances), but when you modify it, each instance will have yours.
  • More important You are running the program in two separate processes. Even if the trick of the class attribute worked, it would only have done so while the objects shared the same memory space. From the moment you launch it in a separate process, it can not share memory and therefore can not share variables.
  • The graphic application must find another way to send the data to the first one. Leaving them in a shared variable is not possible. Other possible approaches:

    • Leave them in a temporary file, which the original script would read once the sub-script has finished.
    • Make the graphic script issue the data in question by its standard output (for example separated by commas). The main script that launches to the other may try to capture the standard output of the other (use popen() instead of os.system() ) and parsear the string you get (separate by commas, etc.)
    • Change the design to use a single process.

    Enlargement

    If you have everything in one process, then you have simpler ways to communicate information between objects.

    The most obvious is to do it through a return . That is, one object invokes a method of another and that other returns the value that the first expects. You do not need shared variables in this case. You can base yourself on the example given in this answer

    You can also use your original approach to save it in a class variable, but in that case you should not use self.variable = dato for it, because as it explained at the beginning of this answer, that creates a new variable in the instance (object ), without modifying the same variable of the class. You should use instead clase.variable = dato (in your case FunctionClass._PDFDatos = [...] ).

    However, I see a problem with this approach. How do you synchronize the execution of step 5? In that step you try to access the class variable but how do you know if that variable has already been set? That will not happen until the user has written something in the GUI, and that is asynchronous in nature. Somehow you have to synchronize those threads.

        
    answered by 08.10.2018 в 10:28
    1

    You should only use a second process or thread if the script and the form should be executed asynchronously, so that when your main script calls the form this is displayed, but the main script continues with its execution doing other things while the user interacts with the form. This implies that you will have to synchronize both processes / threads and share information securely between them, for example through queues.

    However I think you want your dialogue to behave as "modal", thus blocking the execution of the code until the user accepts or closes it and continues from there with the execution of the main script. In that case you are complicating yourself a lot without need. The call to mainloop is a blocking call and the cycle is executed in the same process in which it is launched, thus blocking execution at that point.

    Therefore, you can simply instantiate and launch the form in your CambiarNombrePDF method, which will block the execution of that function at that point until the mainloop ends, at which time you can access the data without problems. the instance of the form. There are many ways to implement it, an example based on your code (there are missing data, but it can be reproduced):

    import tkinter as tk
    
    
    
    class NameForm(tk.Tk):
        def __init__(self):
            super().__init__()
            self.title("Set customer")
            frame = tk.Frame(self)
            frame.grid()
    
            self.user_name = tk.StringVar()
            self.user_last = tk.StringVar()
            self.user_num = tk.StringVar()
            self.file_type = tk.StringVar()
    
            tk.Label(frame, text="Enter customer's Name: ").grid(row=1, column=1, sticky=tk.W)
            tk.Entry(frame, textvariable=self.user_name).grid(row=1, column=200, sticky=tk.W)
            tk.Label(frame, text="Enter customer's Last Name: ").grid(row=2, column=1,
                rowspan=1, columnspan=6, sticky=tk.W)
            tk. Entry(frame, textvariable=self.user_last).grid(row=2, column=200, sticky=tk.W)
            tk.Label(frame, text="Enter customer's Last 4 SSN: ").grid(row=3, column=1,
                rowspan = 1, columnspan = 6, sticky=tk.W)        
            tk.Entry(frame, textvariable = self.user_num).grid(row=3, column=200, sticky=tk.W)
            tk.Label(frame, text="Enter Document Type: ").grid(row=4, column=1, rowspan=1,
                columnspan = 6, sticky=tk.W)       
            tk.Entry(frame, textvariable = self.file_type).grid(row=4, column=200, sticky=tk.W)
            tk.Button(frame, text="ACCEPT", command=self.on_accept).grid(row=6,
                column=3,sticky=tk.E + tk.W)
    
        def on_accept(self):  
            # Puedes aprobechar esto para validar los datos antes de cerrar
            self.destroy()
    
        def get_data(self):
            self.mainloop()
            user_name = self.user_name.get()
            user_last = self.user_last.get()
            user_num = self.user_num.get()
            file_type = self.file_type.get()
            return [user_name, user_last, user_num, file_type]       
    
    
    class FunctionsClass:
    
        def __init__(self):
            self._pdf_datos = []
    
        def cambiar_nombre_pdf(self, dir_pdf, convert_to_pdf):
            print("Lanzando formulario")
            formulario = NameForm()
            self._pdf_datos = formulario.get_data()
            print("Formulario cerrado")
            print(self._pdf_datos)
    
    
    
    if __name__ == "__main__":
        inst = FunctionsClass()
        inst.cambiar_nombre_pdf(None, None)
    

    The class of the form can be placed in another module and imported if you prefer.

    Class attributes are shared among all instances of the class in the process, however, if a referencing occurs through the instance and not through the class, a new instance attribute is created with the same name that leaves intact and overlaps the class attribute for that instance. In this case you do not need a class attribute in principle. For more information you can look at this question:

    Edit

    In response to the questions raised:

    • inst = FunctionsClass() simply creates an instance of the class FunctionsClass and inst.cambiar_nombre_pdf(None, None) calls the instance method cambiar_nombre_pdf , which in your code receives two parameters that I have maintained ( dir_pdf and convert_to_pdf ). These arguments are not used in the function, if you do not use them you must eliminate them. As I do not know very well the utility that you give them, I pass None to both to fulfill the signature of the function simply and that it can be executed. All this I suppose would already take place somewhere in your code that you do not sample. In terms of if __name__ == "__main__" , its usefulness is to include code that will be executed only when the module is executed as the main one, not when it is imported, for more information see:

      What is if __name__ == "__main __":?

    • As for def __init__(self) , it is the initializer of the class, it is explained in more detail in:

      What is a constructor?

    • super().__init__(self) is responsible for calling the parent's initializer ( Frame in your case and Tk in mine). This is necessary because we have overwritten the __init__ inherited from the parent class. For our class to have all the functionality of the parent class that is carried out in its initializer, it is necessary to call it explicitly. The same does Frame.__init__(self, master) , but forces us to hardcodear the name of the parent class. For more information see:

      What is it and what Does utility have super in OOP?

    answered by 08.10.2018 в 21:37