2026, Jan 06 00:02

NameError в Tkinter при привязке кнопки: причины и решение

Почему в Tkinter возникает NameError при привязке кнопки: обращение к глобальному имени, безопасный вызов shutdown через self.master и ловушка grid — разбор

Привязать кнопку Tkinter к закрытию окна кажется пустяком, пока посреди инициализации не выскакивает NameError. Причина кроется в мелочи: внутри конструктора виджета мы ссылаемся на объект приложения по его глобальному имени до того, как это имя вообще появится. Разберёмся, почему так происходит и как аккуратно это исправить.

Сценарий проблемы

Ниже приведён фрагмент, создающий фрейм с кнопкой EXIT. Обработчик привязывается через обращение к объекту верхнего уровня по имени. Именно это и вызывает ошибку во время инициализации.

#!/usr/bin/python3.9
import tkinter as tk
from tkinter import ttk

class ControlPanel(ttk.Frame):
def __init__(self, host):
super().__init__(host)
self._init_controls()

def _init_controls(self):
self.btn = ttk.Button(self, text=' EXIT', command=ui.shutdown).grid(column=0, row=0, sticky='ew')
self.grid(padx=0, pady=0)

class RootWindow(tk.Tk):
def __init__(self):
super().__init__()

self.configure(bg='#ff3800')
self.geometry("500x200")
self.resizable(False, False)

self.columnconfigure(0, weight=1)
self._compose()

def _compose(self):
panel = ControlPanel(self)
panel.grid(column=0, row=0)

def shutdown(self):
self.destroy()

if __name__ == "__main__":
ui = RootWindow()
ui.mainloop()

Почему возникает NameError

Имя, которое вы используете для экземпляра приложения, связывается только после возврата из инициализатора подкласса Tk. Иными словами, пока конструктор ещё выполняется, глобальное имя не определено. Цепочка инициализации выглядит так: создание приложения запускает его __init__, в котором строятся виджеты и формируется фрейм; внутри настройки фрейма вы пытаетесь использовать глобальное имя приложения для команды. В этот момент имя ещё не присвоено, поэтому Python выбрасывает NameError.

Есть ещё одна ловушка: присваивание результата .grid(...) переменной. Менеджеры геометрии вроде grid не возвращают виджет — они не возвращают ничего. В результате self.btn становится None. Используйте grid только для размещения виджета, а не для его получения.

Как исправить

Глобальное имя приложения не требуется. Нужный экземпляр уже доступен через ссылку на родителя. Tkinter сохраняет контейнер, переданный в Frame, в атрибуте master, поэтому метод shutdown можно вызывать через self.master. Это снимает проблемы с моментом определения глобальных имён и делает зависимости локальными.

#!/usr/bin/python3.9
import tkinter as tk
from tkinter import ttk

class ControlPanel(ttk.Frame):
def __init__(self, host):
super().__init__(host)
self._init_controls()

def _init_controls(self):
self.btn = ttk.Button(self, text=' EXIT', command=self.master.shutdown)
self.btn.grid(column=0, row=0, sticky='ew')
self.grid(padx=0, pady=0)

class RootWindow(tk.Tk):
def __init__(self):
super().__init__()

self.configure(bg='#ff3800')
self.geometry("500x200")
self.resizable(False, False)

self.columnconfigure(0, weight=1)
self._compose()

def _compose(self):
panel = ControlPanel(self)
panel.grid(column=0, row=0)

def shutdown(self):
self.destroy()

if __name__ == "__main__":
ui = RootWindow()
ui.mainloop()

Почему это важно

Понимание того, когда именно связываются имена при создании объектов, помогает избежать хрупких участков кода во время инициализации GUI. Опора на self.master делает зависимости явными и безопасными на всём пути вызовов конструктора. Разделение создания виджета и управления геометрией гарантирует, что вы сохраняете ссылки на виджеты, а не случайно заменяете их на None.

Что запомнить

Не обращайтесь к приложению по глобальному имени изнутри виджетов, создаваемых во время его инициализации. Используйте ссылку на контейнер и вызывайте методы через self.master. А когда нужна ссылка на виджет, сначала создайте его, а grid вызывайте потом. Эти небольшие правки убирают NameError и делают код легче сопровождать.