Flake8 plugin to detect (too) common mistakes and bad practices in Tkinter projects
Project description
flake8-tkinter
A flake8 plugin that helps you detect (too) common mistakes and bad practices in you Tkinter project
Project idea by @insolor
Installation
pip install flake8-tkinter
List of warnings
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
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)TK231
: 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)TK232
: 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)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)
Examples
TK102
# Bad
def foo():
top = tk.Toplevel()
...
top.mainloop()
root.mainloop()
# Good
def foo():
top = tk.Toplevel()
...
root.mainloop()
TK111
# 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)
TK112
# 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=...))
TK131
# Bad
btn = tk.Button().grid()
# Good
btn = tk.Button()
btn.grid()
TK201
# Bad
from tkinter import *
# Good
import tkinter
# OR
import tkinter as tk
TK202
# Bad
from tkinter.ttk import *
# Good
from tkinter import ttk
TK211
# Bad
import tkinter.ttk as ttk
# Good
from tkinter import ttk
TK221
# 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)
TK231
Will be renamed to TK141 in v1.0.0
# Bad
w.bind("<Button-1>", foo)
# Good
w.bind("<Button-1>", foo, add=True)
# OR
w.bind("<Button-1>", foo, add=False)
TK232
Will be renamed to TK142 in v1.0.0
# 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()`
TK251
Yes, there's some minor diffrence in text wrapping difference, but that can be adjusted
# Bad
w = tkinter.Message()
# Good
w = tkinter.Label()
TK304
# Bad
w.bind("<Button-1>", foo, add="+")
# Good
w.bind("<Button-1>", foo, add=True)
TK504
# Bad
w.pack(side=tkinter.BOTTOM, fill=tkinter.BOTH)
# Good
w.pack(side="bottom", fill="both")
Planned warnings
-
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 callable (lol)TK121
: 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()
.TK141
: Suggest keeping reference of localPhotoImage
instance to avoid GC.
-
Cross platform (TK181-TK199)
TK181
: Using<Shift-Tab>
binding. It doesn't work on Linux.
-
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.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
)TK503
: Using subscripting for widget cget and configure. Use.cget()
and.configure()
instead.
Development
- 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
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
flake8_tkinter-0.5.3.tar.gz
(10.9 kB
view hashes)