Search code examples
pythontkintermainloop

Python + Tkinter: "AttributeError"


I also have not a lot of practice with Python and have a fundamental problem of understanding the error: AttributeError: 'NoneType' object has no attribute '_root', which only appears, when I define the dec variable BEFORE defining the main window win:

import tkinter as tk
from tkinter import ttk
from tkinter import *

# This variable must be defined AFTER definition of the Tk() window!
dec = tk.BooleanVar()

# Main window
win = Tk()

# # This variable must be defined AFTER definition of the Tk() window!
# dec = tk.BooleanVar()


decreaseButton = Checkbutton(win, text = "Decrease (optional)", variable = dec)
decreaseButton.grid(row=1, column=1, sticky='W')


# Runs the event loop of Tkinter
win.mainloop()

Why do I have to define first the window and than the Boolean variable? What did I not understand from Tkinter?

Thank you everybody for your great help and with best wishes Lars


Solution

  • You can actually look this up at tkinter's __init__.py.

    StringVar, IntVar, DoubleVar and BooleanVar all inherits from the class Variable:

    class Variable:
        ...
        _default = ""
        _tk = None
        _tclCommands = None
        def __init__(self, master=None, value=None, name=None):
            ...
            if name is not None and not isinstance(name, str):
                raise TypeError("name must be a string")
            global _varnum
            if not master:
                master = _default_root
            self._root = master._root()
            self._tk = master.tk
    
        ...
    

    So you see when a tkinter variable is created, it will lookup for a master stored as a global variable _default_root (which is None if you have yet to create a tk instance), which is why you receive a AttributeError.

    But you might ask, why the same does not apply to widgets? That is because Widgets inherits from a different base class called BaseWidgets:

    class BaseWidget(Misc):
        ...
        def _setup(self, master, cnf):
            ...
            if _support_default_root:
                global _default_root
                if not master:
                    if not _default_root:
                        _default_root = Tk() <--- create a new instance of `Tk`
                    master = _default_root
    

    So you see when you create a new widget without a master, BaseWidget will actually create a new instance of tk as _default_root as opposed to Variable. My guess is that there is no reason to create an instance of Tk just for a variable since nothing needs to be rendered on screen, but the same cannot be applied for a widget.

    As such, the below doesn't throw an error even you did not create a Tk instance yourself:

    import tkinter as tk
    
    a = tk.Button(text="ABC")
    
    b = tk.BooleanVar()