1

Hola a todos soy nueva en python, espero me puedan ayudar, el problema que tengo es el siguiente.

tengo un formulario (tkinter) .pyw en donde recojo unos datos y pretendo llevarlos a una función independiente que corre en paralelo desde un py.

estoy tratando de pasar los datos a través de una función utilizando una lista pero los datos no llegan... paso a explicar mejor con código y espero puedan orientarme en esto gracias...

en el código he colocado >>>PASOS<<< para mostrar el flujo de ejecución

los pasos 1, 2, 4, 5 se ejecutan en la funcion 1 (archivo .py) y el paso 3 se ejecuta en la funcion 2 (archivo .pyw)

función 1 archivo de nombre 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]

funcion 2 archivo de nombre RootForm.pyw

esta app es llamada desde la funcion "GetName()" en el archivo functions.py y adicionalmente importa al functions.py para poder usar la función ModNames() para devolver los valores:

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., vuelvo a colocar el comentario que borre por recomendación de los moderadores, para que otros lectores puedan hacer seguimiento del hilo: muchísimas gracias Abulafiabpor tú interés, como había comentado antes, es cierto lo que planteas de lo de la memoria y me di cuenta cuando empecé a chequear el I’d de memoria de la variable id(self._PDFdatos) cuando accedía a dicha variable desde la app para modificarla y chequeaba id en efecto era distinto a cuando la abría desde es script.

Al darme cuenta de esto pase a integrar ambos archivos tanto el .pyw y el .py y ahora llamo a la app gráfica directamente desde el script original lo que teóricamente me evita estar ejecutando dos procesos distintos por lo que ahora popen() ya no tiene sentido ya que están como parte del código insertado en el mismo .py yo he querido descartar la escritura de datos en disco ya que este proceso debe chequear la carpeta cada 5 segundos eso trae mucho desgaste al disco ssd del cliente, crees que exista una forma de pasar los datos desde la app gráfica al código o mi única opción es finalmente escribirlos en el disco? Gracias!!

Actualización 2 Voy a intentar lo que me planteas del return, ya que la otra opción o respuestas que sugieres no me aplica mucho ya que en efecto yo si estoy captando los datos que ingresa el usuario ya que use la _PDFfileType=StringVar() desde un principio y con el get() si veo los datos inclusive los puedo manipular, pero sólo del lado de la app( dado lo que mencionas de que la clase app creo su propia instancia y su propia variable...

Avisaré como me va..., gracias

Actualizacion 3

hola FJSevilla, los procedimientos que no entendí son :

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

y este otro:

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

sin embargo leyendo ampliamente tu respuesta conseguí el link que me pasaste de la diferencia entre atributos de instancias y atributos de clase así que creo que por ahí es que voy a continuar investigando. una vez mas gracias... :)

Yoka R
  • 61
  • 5

2 Answers2

1

Si no entiendo mal, tu flujo de ejecución sería:

  1. Lanzar el archivo functions.py mediante un comando del estilo python functions.py (supongo).
  2. Ese script, mediante código que no muestras en la pregunta, instancia un objeto de la clase FunctionsClass y llama a su método CambiarNombrePDF().
  3. Esta función (a través de GetName()) lanza otro script contenido en el fichero RootForm.pyw. Este otro script tiene por misión pedir al usuario ciertos datos a través de una interfaz gráfica.
  4. Pretendes que los datos introducidos por el usuario lleguen al objeto FunctionsClass que habías instanciado en el paso 2. Pero para ello haces un from functions import * e instancias un nuevo objeto de tipo FunctionsClass, en el cual llamas a ModNames() y terminas.
  5. De nuevo en el objeto original esperas que los cambios que ModNames() hizo en el otro objeto, durante el paso 4, hayan aparecido "mágicamente" en este otro objeto.

¿Es correcto?

Ese enfoque no puede funcionar, por dos razones:

  1. Aunque declaras _PDFdatos como un atributo de clase (supongo que con la esperanza de que sea compartido por todos los objetos instanciados desde esa clase), cuando lo modificas mendiante self._PDFdatos, se estará creando un nuevo atributo de objeto, en lugar de cambiar el valor al atributo de la clase. Mientras sólo lo leas, estarás leyendo el de la clase (igual para todas las instancias), pero cuando lo modifiques, cada instancia tendrá el suyo.
  2. Más importante aún. Estás corriendo el programa en dos procesos separados. Aún si el truco del atributo de clase hubiera funcionado, sólo lo habría hecho mientras los objetos compartan el mismo espacio de memoria. Desde el momento en que lo lanzas en un proceso separado, no pueden compartir memoria y por tanto no pueden compartir variables.

La aplicación gráfica debe encontrar otra forma de enviar a la primera los datos. Dejarlos en una variable compartida no es posible. Otros posibles enfoques:

  • Dejarlos en un fichero temporal, que el script original leería una vez el sub-script haya terminado.
  • Hacer que el script gráfico emita por su salida estándar los datos en cuestión (por ejemplo separados por comas). El script principal que lanza al otro puede tratar de capturar la salida estándar del otro (usa popen() en lugar de os.system()) y parsear la cadena que obtenga (separar por las comas, etc.)
  • Cambiar el diseño para usar un único proceso.

Ampliación

Si tienes todo en un solo proceso, entonces tienes formas más simples de comunicar información entre objetos.

La más obvia es hacerlo a través de un return. Es decir, un objeto invoca un método de otro y ese otro retorna el valor que el primero espera. No necesitas en este caso variables compartidas. Puedes basarte en el ejemplo dado en esta respuesta

También puedes usar tu enfoque original de guardarlo en una variable de clase, pero en ese caso no debes usar self.variable = dato para ello, ya que como explicaba al principio de esta respuesta, eso crea unna nueva variable en la instancia (objeto), sin modificar la misma variable de la clase. Deberías usar en cambio clase.variable = dato (en tu caso FunctionClass._PDFDatos = [...]).

No obstante veo un problema en este enfoque. ¿Cómo sincronizas la ejecución del paso 5? En ese paso intentas acceder a la variable de clase pero ¿cómo sabes si esa variable ya ha sido fijada? Eso no ocurrirá hasta que el usuario haya escrito algo en la GUI, y eso es de naturaleza asíncrona. De algún modo tienes que sincronizar esos hilos.

abulafia
  • 53,696
  • 3
  • 45
  • 80
1

Solo debes usar un segundo proceso o hilo si el script y el formulario deben ejecutarse de forma asíncrona, de forma que cuando tu script principal llame al formulario este se muestre, pero el script principal siga con su ejecución haciendo otras cosas mientras el usuario interactúa con el formulario. Esto implica que vas a tener que sincronizar ambos proceso/hilos y compartir información de forma segura entre ellos, por ejemplo mediante colas.

No obstante creo que quieres que tu diálogo se deba comportar como "modal", bloqueando por tanto la ejecución del código hasta que el usuario lo acepte o cierre y continuando a partir de ahí con la ejecución del script principal. En ese caso te estás complicando mucho sin necesidad. La llamada a mainloop es una llamada bloqueante y el ciclo se ejecuta en el mismo proceso en el que se lanza, bloqueando por tanto la ejecución en ese punto.

Por lo tanto, puedes simplemente instanciar y lanzar el formulario en tu método CambiarNombrePDF, lo cual bloquerá la ejecución de dicha función en ese punto hasta que el mainloop termine, momento en el que puedes acceder sin problemas a los datos de la instancia del formulario. Hay muchas formas de implementarlo, un ejemplo basado en tu código (hay datos que me faltan, pero se puede reproducir):

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)

introducir la descripción de la imagen aquí

La clase del formulario puedes colocarla en otro módulo e importarla si lo prefieres.

Los atributos de clase son compartidos entre todas las instancias de la clase en el proceso, no obstante, si se produce una raesignación haciendo referencia a través de la instancia y no mediante la clase se crea un nuevo atributo de instancia con el mismo nombre que deja intacto y solapa el atributo de clase para esa instancia. En este caso no necesitas un atributo de clase en principio. Para más información puedes mirarte esta pregunta:


Edición

En respuesta a las dudas planteadas:

  • inst = FunctionsClass() simplemente crea una instancia de la clase FunctionsClass y inst.cambiar_nombre_pdf(None, None) llama al método de instancia cambiar_nombre_pdf, el cual en tu código recibe dos parámetros que he mantenido (dir_pdf y convert_to_pdf). Dichos argumentos no son usados en la función, si no los usas debes eliminarlos. Como no se muy bien la utilidad que les das, les paso None a ambos para cumplir con la firma de la función simplemente y que se pueda ejecutar. Todo esto supongo que ya tendría lugar en alguna parte de tu código que no muestras. En cuanto a lo de if __name__ == "__main__" su utilidad es englobar código que será ejecutado solo cuando el módulo se ejecute como principal, no cuando se importe, para más información ver:

    ¿Qué es if __name__ == “__main__”:?

  • En cuando a def __init__(self), es el inicializador de la clase, se explica más detalladamente en:

    ¿Qué es un constructor?

  • super().__init__(self) se encarga de llamar al inicializador del padre (Frame en tu caso y Tk en el mio). Esto es necesario porque hemos sobreescrito el __init__ heredado de la clase padre. Para que nuestra clase tenga toda la funcionalidad de la clase padre que se lleva a cabo en su inicializador, es necesario llamarlo explícitamente. Lo mismo hace Frame.__init__(self, master), pero nos obliga a hardcodear el nombre de la clase padre. Para más información ver:

    ¿Qué es y que utilidad tiene super en POO?

FJSevilla
  • 55,603
  • 7
  • 35
  • 58
  • Hola **FJSevilla**, tu solución funciono perfectamente, literalmente lo que estaba buscando ya que no quería hacer escritura de datos en disco ya lo estoy golpeando mucho al tener que chequear constantemente si hay nuevos archivos agregados, también le agradezco a **@abulafia** por su aporte ya que sus respuestas estaban orientándome hacia esta dirección... gracias a ambos... – Yoka R Oct 09 '18 at 01:48
  • **FJSevilla** adicionalmente debo admitir que desconozco por completo dos planteamiento que hiciste, voy a agregar una **Actualizacion 3** en mi pregunta en donde señalare aquello que no se que hiciste, me gustaría, si puedes, me comentes que nombre recibe ese procedimiento y **_así yo puedo buscar información en internet para aprender mas sobre eso_ ** como dije antes estoy aprendiendo python por mi cuenta, una vez mas gracias a ambos – Yoka R Oct 09 '18 at 01:48
  • @YokaR he editado la respuesta, mira al final. Casi todo son enlaces a preguntas que ya existen en el sitio que tratan de forma específica lo que se usa en las líneas sobre las que tienes dudas. Si aún así sigues teniendo alguna duda comenta. Un saludo. – FJSevilla Oct 09 '18 at 03:36
  • Excelente info, gracias... – Yoka R Oct 09 '18 at 09:43