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_handler
with arguments, use lambda or functools.partial. (example)TK131
: Assigning result of geometry manager call to a variable. (example)
Best practices
TK141
: Using bind withoutadd=True
will overwrite any existing bindings to this sequence on this widget. Either overwrite them explicitly withadd=False
or useadd=True
to 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 tk
or simplyimport tkinter
instead. (example)TK202
: Usingfrom tkinter.ttk import *
is generally a bad practice and discouraged. Usefrom tkinter import ttk
instead. (example)TK211
: Usingimport tkinter.ttk as ttk
is pointless. Usefrom tkinter import ttk
instead. (example)TK221
: Using tkinter.TRUE, tkinter.FALSE, etc. is pointless. Use an appropriate Python boolean instead. (example)TK251
: Usingtkinter.Message
widget. It's redundant sincetkinter.Label
provides the same functionality. (example)
Code quality
TK304
: Value foradd
in 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.Tk
instances. 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 localPhotoImage
instance to avoid GC.TK151
: Don't usew.setup()
directly. Use init args, orw.configure()
.- Extend
TK111
andTk112
to 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_visibility
beforewm_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.W
and 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.Menu
withouttearoff=False
TK261
: Using subsequentwm_attributes
calls. 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.Text
indexes, likeend - 1 chars
instead ofend-1c
.TK303
: Using a float astkinter.Text
index. 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
flake8
andpytest
in it - Run
pip install -e .
to installflake8-tkinter
in editable format - Run
python3 -m pytest
to test your changes