[Python-es] NameError: name 'root' is not defined

Miguel Sanchez miguel en mariaonline.org
Dom Abr 23 14:01:53 EDT 2023


El Sat, 22 Apr 2023 15:52:25 -0300
Horacio <horacio9573 en gmail.com> dijo:

> Buenas Tardes tengo una duda de porque sale este error,,
> 
> Exception in Tkinter callback Traceback (most recent call last): File
> "/usr/lib/python3.10/tkinter/__init__.py", line 1921, in call return
> self.func(*args) File
> "/home/hcaste/Escritorio/investigar/Proyecto_Florencia/Programas/Interface/imv02.py",
> line 24, in salir root.destroy() NameError: name 'root' is not defined


Hola.

Estas usando root dentro del método salir. Pero ahí root está fuera del alcance del método.

El método es un callback de tu botón salir. Se ejecutó cuando le hiciste clic con el botón izquierdo del ratón al botón que tienes definido en el widget.

Te recomiendo uses los widgets ttk en lugar de los clásicos y definas su apariencia mediante el atributo style. También te recomiendo que uses la geometría gird en lugar de la pack. A la larga te resultará más fácil.

Dale un vistazo a la documentación en linea que recomiendan en la doc de Python: https://docs.python.org/es/3/library/tkinter.html especialmente al primer enlace.

Aquí te dejo una versión ligeramente reformada de tú código.
La clase deriva ahora de ttk.Frame, con lo que estas heredando toda la maquinaria de tkinter en tu clase, por eso le puedes hacer app.mainloop a la instancia que creas.

Un detalle: self.winfo_toplevel() te devuelve la ventana más externa. Esta no es la ventana de tu app (es decir la ventana de la instancia que has creado al instanciar la clase cuando has hecho app = Img(tk.Tk()). Este self.winfo_toplevel() es el padre de tu instancia. Es un "toplevel" (se ha creado "mágicamente" cuando has instanciado la clase Tk) y le puedes aplicar los métodos de toplevel como .title (un Frame no tiene .title). Cuando instancias la clase, la instancia (self) es un Frame que es hijo de master (master y top en mi código son lo mismo). Pero es en top donde muestras tus widgets. El mainloop lo ejecutas sobre la instancia y a la hora de matarlo, lo haces con self.destroy()

Valora poner la creación de los widgets en un método separado del __init__ y es por eso por lo que uso top = self.winfo_toplevel() pues recupero en el método el "master" del __init__. El métódoo __init__ lo dejo para definir otros atributos de la instancia, 


import sys

import tkinter as tk
from tkinter import ttk


class Img(ttk.Frame):
    """
    La clase
    """

    def __init__(self, master):
        super().__init__(master)

        self._crear_widgets()

    def _crear_widgets(self):
        top = self.winfo_toplevel()  # Esto es equivalente a master
        top.resizable(height=tk.FALSE, width=tk.FALSE)
        top.title('Imagen en Tk')
        top.config(background='spring green', relief='sunken', bd=10)
        top.protocol('WM_DELETE_WINDOW', self._salir)
        top.option_add('*tearOff', tk.FALSE)

        marco = ttk.Frame(top, borderwidth=5, width=800, height=455)
        marco.grid(row=0, column=0)

        s = ttk.Style()
        s.configure('Miboton.TButton', background='red2', foreground='yellow', borderwidth=5)
        boton_salir = ttk.Button(top, text='Salir', style='Miboton.TButton', command=self._salir)
        boton_salir.grid(row=1, column=0, sticky='E')

    def _salir(self, envento=None):
        self.destroy()
        sys.exit()


# Prefiero llamar a la aplicación así:
if __name__ == '__main__':
    app = Img(tk.Tk())
    app.mainloop()

Derivar la clase principal de tu aplicación de ttk.Frame es útil cuando generas ventanas secundarias hijas de la principal. Si no quieres que tu clase esté derivada de ttk.Frame debes aplicar el mainloop a la instancia de tk, asignarla a un atributo en el __init__ para poder usarla en los métodos y destruirla cuando terminas con destroy. Podrías hacerlo así:


import sys

import tkinter as tk
from tkinter import ttk


class Img():
    """
    La clase
    """

    def __init__(self, raiz):
        self.root = raiz
        self._crear_widgets()

    def _crear_widgets(self):
        top = self.root.winfo_toplevel()
        top.resizable(height=tk.FALSE, width=tk.FALSE)
        top.title('Imagen en Tk')
        top.config(background='spring green', relief='sunken', bd=10)
        top.protocol('WM_DELETE_WINDOW', self._salir)
        top.option_add('*tearOff', tk.FALSE)

        marco = ttk.Frame(top, borderwidth=5, width=800, height=455)
        marco.grid(row=0, column=0)

        s = ttk.Style()
        s.configure('Miboton.TButton', background='red2', foreground='yellow', borderwidth=5)
        boton_salir = ttk.Button(top, text='Salir', style='Miboton.TButton', command=self._salir)
        boton_salir.grid(row=1, column=0, sticky='E')

    def _salir(self, envento=None):
        self.root.destroy()
        sys.exit()


# Prefiero llamar a la aplicación así:
if __name__ == '__main__':
    raiz = tk.Tk()
    app = Img(raiz)
    raiz.mainloop()


Saludos


Miguel Sánchez












Más información sobre la lista de distribución Python-es