E
It's just a problem with variables. Any variable that is believed in a function/method or is intended to reassign is treated by default as a local variable to its function/method, so it is only accessible from that function/method but never from outside.The problem is that in the closing anotar reassigning to the variable lista_datos defined opcionales. You could fix it with the use of nonlocal:def anotar():
nonlocal lista_datos
However, there are more problems in your implementation:Cute time it's called opciones all the data is added again, with it you would double the labels with the data of the array, including the three that act as head of the table.If you use numpy.insert, the second argument is the index where to insert.You think you're inserting rows and creating a 2D array, but no, that's not how it works. numpy.insert or numpy.append:>>> lista_datos = np.array([])
>>> lista_datos = np.insert(lista_datos, 0, np.array([1, 2, 3]))
>>> lista_datos = np.insert(lista_datos, 0, np.array([4, 5, 6]))
>>> lista_datos
array([4., 5., 6., 1., 2., 3.])
>>> lista_datos.shape
(6,)
Deriving from the above, since you have a 1D array the way you try to iterate about the array is wrong.Numpy arrays are perfect and very efficient structures to store numerical data and operate with them, but they are not intended to be containers to which we are constantly adding or deleting data. Every time you do something like that the entire array has to be copied in memory. In these cases, a Python list (which simply stores references to arbitrary objects) is an infinitely more efficient structure.In my opinion you are too complicated by generating closures in the initializer, which on the other hand makes it difficult to read and maintain the code. When, as in this case, the code is complied with, it applies the almost always infallible "divide and beat", divides each form, window, etc into classes, that each class takes care of its own and then in each initializer dedicate itself to instance and build by composition, leaving it as "clean" as possible.In addition, this will allow you to reuse classes as many times as you want or extend them for inheritance if you need it in the future.For example, restructuring the app quite well, you could do something like:import tkinter as tk
class Datos(tk.Frame):
def __init__(self, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
# Widgets para los datos de entrada:
self.nombre = tk.StringVar(self)
self.apellido = tk.StringVar(self)
tk.Label(
self, text='Ingrese datos:'
).grid(row=0, column=0, columnspan=None, sticky='nsew')
tk.Label(self, text='Nombre:').grid(row=1, column=0, sticky='e')
tk.Label(self, text='Apellido:').grid(row=2, column=0, sticky='e')
self._nombre_entry = tk.Entry(self, textvariable=self.nombre)
self._nombre_entry.grid(row=1, column=1, sticky='w')
self._apellido_entry = tk.Entry(self, textvariable=self.apellido)
self._apellido_entry.grid(row=2, column=1, sticky='w')
self._accept_btn = tk.Button(
self, text='¡Siguiente!', command=self.on_accept
)
self._accept_btn.grid(row=4, column=0, columnspan=2)
def on_accept(self):
self.event_generate("<<NewDataAccept>>")
self._accept_btn.config(state="disabled")
self._nombre_entry.config(state="readonly")
self._apellido_entry.config(state="readonly")
class DatosOpcionales(tk.Frame):
def __init__(self, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
# Variable para las opciones
self._op = tk.IntVar(self, value=1)
# Lista con la tabla de datos
self._datos = []
# Cabeceras de la tabla
self._headers = ['Materia', 'Nota', 'Profesor']
self._headers_labels = [
tk.Label(self, text=header) for header in self._headers
]
# Widgets para elegir opción de formato de ingreso
tk.Label(
self, text='Agregar opcionales'
).grid(row=2, column=0, columnspan=2)
tk.Radiobutton(self, value=1, variable=self._op).grid(row=3, column=0)
tk.Radiobutton(self, value=2, variable=self._op).grid(row=4, column=0)
tk.Label(self, text='Nota/Profesor').grid(row=3, column=1)
tk.Label(self, text='Profesor/Nota').grid(row=4, column=1)
# Este botón sirve para añadir más datos (opcionales)
tk.Button(
self, text='Añadir', command=self.agregar_opcionales
).grid(row=5, column=0)
def agregar_opcionales(self):
if self._op.get() == 1:
campos = ["Materia", "Nota", "Profesor"]
else:
campos = ["Materia", "Profesor", "Nota"]
ventana = VentanaNuevosDatosOps(self, campos=campos)
ventana.bind("<<NewOptionalDataAccept>>", self._agregar_datos)
def _agregar_datos(self, event):
if not self._datos:
for i, header in enumerate(self._headers_labels):
header.grid(row=6, column=i, sticky='nsew')
data = event.widget.data
nuevos_datos = [data.get(header) for header in self._headers]
self._datos.append(nuevos_datos)
row = len(self._datos) + 6
for i, dato in enumerate(nuevos_datos):
tk.Label(self, text=dato).grid(row=row, column=i, sticky='nsew')
class VentanaNuevosDatosOps(tk.Toplevel):
def __init__(self, parent, campos, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.resizable(False, False)
# Asumo Python >= 3.7
self.data = dict.fromkeys(campos)
self._entries = []
tk.Label(self, text='Ingrese datos opcionales correctamente:'
).grid(row=0, column=0, columnspan=2)
for i, campo in enumerate(campos):
i += 1
tk.Label(self, text=f"{campo}:").grid(row=i, column=0, sticky='e')
entry = tk.Entry(self)
entry.grid(row=i, column=1, sticky='w')
self._entries.append(entry)
tk.Button(
self, text='Anotar', command=self.on_accept
).grid(row=4, column=0, sticky='nsew')
def on_accept(self):
for campo, entry in zip(self.data, self._entries):
self.data[campo] = entry.get()
self.event_generate("<<NewOptionalDataAccept>>")
self.destroy()
class Principal(tk.Frame):
def __init__(self, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
# Formulario datos iniciales
datos = Datos(self)
datos.grid(row=0, column=0)
datos.bind("<<NewDataAccept>>", self.siguiente)
# Text para mostrar datos
self._text = tk.Text(self, width=20, height=5)
def siguiente(self, event):
self._text.grid(row=1, column=0, columnspan=2)
# Luego de pulsar la tecla "siguiente" se mostrará un saludo
nombre = event.widget.nombre.get()
apellido = event.widget.apellido.get()
frase = f'¡Hola, {nombre} {apellido}!'
self._text.insert(1.0, frase)
self._text.config(state='disabled')
resultado_fm = DatosOpcionales(self)
resultado_fm.grid(row=2, column=0)
class App:
def __init__(self):
self.root = tk.Tk()
# Contenedor principal
self._cont_fm = Principal(self.root)
self._cont_fm.pack(fill='both', expand=True)
def mainloop(self):
self.root.mainloop()
if name == 'main':
ejemplo = App()
ejemplo.mainloop()
Some observations:I have chosen to use custom events to communicate every widget with the father and inform him that the form has been accepted. There are more possibilities, but this is quite general and allows you things like having several open secondary windows by sending data to the main one.In the case of the first form with the first name and last name, I chose to disable widgets once pressed next. If you want another behavior you need to define it in your callback, otherwise you will create new widgets by overlapping the previous ones if the user presses the button again.It would be necessary to validate the forms, to send empty forms or missing fields. When tk.Toplevel pop-up windows are created, are the variables created within them stored in memory?Behavior is the same as with any other object in Python, a widget will not be destroyed by memory until the GC does so and the GC will only destroy it when there are no references to that object or these are circular.That is, as long as you keep a living reference to a widgets it will still exist in memory even when it is not shown.