A flake8 plugin that helps you write better Tkinter code
Project idea by @insolor
pip install flake8-tkinter
Common mistakes
TK102: Using multiple mainloop calls is unnecessary. One call is perfectly enough. (example)TK111: Callingcallback_handler()instead of passing the reference for on-click or binding callback. (example)TK112: Callingcallback_handler()with arguments instead of passing the reference for on-click or binding callback. If you need to callcallback_handlerwith arguments, use lambda or functools.partial. (example)TK131: Assigning result of geometry manager call to a variable. (example)
Best practices
TK141: Using bind withoutadd=Truewill overwrite any existing bindings to this sequence on this widget. Either overwrite them explicitly withadd=Falseor useadd=Trueto keep existing bindings. (example)TK142: Creating tag bindings in a loop can lead to memory leaks. Store the returned command names in a list to clean them up later. (example)TK201: Usingfrom tkinter import *is generally a bad practice and discouraged. Useimport tkinter as tkor simplyimport tkinterinstead. (example)TK202: Usingfrom tkinter.ttk import *is generally a bad practice and discouraged. Usefrom tkinter import ttkinstead. (example)TK211: Usingimport tkinter.ttk as ttkis pointless. Usefrom tkinter import ttkinstead. (example)TK221: Using tkinter.TRUE, tkinter.FALSE, etc. is pointless. Use an appropriate Python boolean instead. (example)TK251: Usingtkinter.Messagewidget. It's redundant sincetkinter.Labelprovides the same functionality. (example)
Code quality
TK304: Value foraddin bind methods should be a boolean. (example)
Opinionated warnings
TK504: Using a tkinter constant. Use a string literal instead (disabled by default). (example)
# Bad
def foo():
top = tk.Toplevel()
...
top.mainloop()
root.mainloop()
# Good
def foo():
top = tk.Toplevel()
...
root.mainloop()# Bad
tk.Button(..., command=foo())
button.config(command=bar())
button.bind("<Button-3>", baz())
# Good
tk.Button(..., command=foo)
button.config(command=bar)
button.bind("<Button-3>", baz)# Bad
tk.Button(..., command=foo(arg, kwarg=...))
button.config(command=bar(arg, kwarg=...))
button.bind("<Button-3>", baz(arg, kwarg=...))
# Good
tk.Button(..., command=lambda: foo(arg, kwarg=...))
button.config(command=lambda: bar(arg, kwarg=...))
button.bind("<Button-3>", lambda e: baz(arg, kwarg=...))# Bad
btn = tk.Button().grid()
# Good
btn = tk.Button()
btn.grid()# Bad
from tkinter import *
# Good
import tkinter
# OR
import tkinter as tk# Bad
from tkinter.ttk import *
# Good
from tkinter import ttk# Bad
import tkinter.ttk as ttk
# Good
from tkinter import ttk# Bad
w.pack(expand=tk.TRUE)
w.pack(expand=tk.FALSE)
w.pack(expand=tk.YES)
w.pack(expand=tk.NO)
w.pack(expand=tk.ON)
w.pack(expand=tk.OFF)
# Good
w.pack(expand=True)
w.pack(expand=False)# Bad
w.bind("<Button-1>", foo)
# Good
w.bind("<Button-1>", foo, add=True)
# OR
w.bind("<Button-1>", foo, add=False)# Bad
for index, foo in enumerate(foos):
w.tag_bind(f"bar_{index}", "<Button-1>", baz)
# Good
for index, foo in enumerate(foos):
tcl_command = w.tag_bind(f"bar_{index}", "<Button-1>", baz)
bindings.append(tcl_command) # Clean them up later with `.deletecommand()`_Yes, there's some minor diffrence in text wrapping, but that can be easily fixed
# Bad
w = tkinter.Message()
# Good
w = tkinter.Label()# Bad
w.bind("<Button-1>", foo, add="+")
# Good
w.bind("<Button-1>", foo, add=True)# Bad
w.pack(side=tkinter.BOTTOM, fill=tkinter.BOTH)
# Good
w.pack(side="bottom", fill="both")-
Common mistakes (TK101-TK179)
TK101: Using multipletkinter.Tkinstances. Child windows must be created fromtkinter.Toplevel.TK103: Suggest refactoring code that uses.update(), as it's usually pointless, potentially harmful, and considered a code smell.TK113: Callback handler should be a callableTK121: Usingtime.sleep()in tkinter code. Use.after()in some form instead.TK122: Using an infinite loop in callback handler. Propose to use recursive function with.after().TK???: Suggest keeping reference of localPhotoImageinstance to avoid GC.TK151: Don't usew.setup()directly. Use init args, orw.configure().- Extend
TK111andTk112to check inw.after()calls.
-
Cross platform (TK181-TK199)
TK181: Using<MouseWheel>binding. It doesn't work on Linux with Tk 8.6 (use button4-5 instead)TK182: Using<Shift-Tab>binding. It doesn't work on Linux (use<ISO_Left_Tab>instead)TK183: Using<Menu>binding. It doesn't work on Windows (use<App>instead)TK184: Binding to control or alt with constant values. It probably won't work on macOS.TK191: Not callingwait_visibilitybeforewm_attributes("-alpha").TK192: Usingw.state("zoomed"). It throws an error on Linux (and on mac too?). Usewm_attributes("-zoomed", True)
-
Best practices (TK201-TK299)
TK222: Usingtk.N+tk.S+tk.E+tk.Wand combinations like that. Usetk.NSEW, or some other constant instead.TK241: Creating a widget without parent specified, and there is a container in the same scope.TK252: Usingtkinter.Menuwithouttearoff=FalseTK261: Using subsequentwm_attributescalls. It can take value pairs.
-
Code quality (TK301-TK399)
TK301: Suggest using more clear binding sequences, like<Button-1>instead of<1>and<Key-a>instead of<a>.TK302: Suggest using more cleartkinter.Textindexes, likeend - 1 charsinstead ofend-1c.TK303: Using a float astkinter.Textindex. It works because how Tkinter translates Python objects to Tcl, but it shouldn't.
-
OO (TK401-TK499)
TK401: Consider refactoring a huge app with OOP.TK402: Consider refactoring widget into separate class.
-
Opinionated rules (TK501-TK599)
TK501: Callingmainloop()on something other than the root window.TK502: Using things likeroot.wm_title(). Useroot.title(). (But there should be exceptions, likewm_attributes, and instead warn on plainattributes)TK503: Using subscripting for widget cget and configure. Use.cget()and.configure()instead.
- Clone the repo
- Set up a virtual environment, activate, and install
flake8andpytestin it - Run
pip install -e .to installflake8-tkinterin editable format - Run
python3 -m pytestto test your changes