EDIT: I have submitted this as a feature improvement proposal via the GitHub issue tracker for cpython. See Issue 100891.
N.B.: I recognize that this question may border on "opinion-based" - if there is a better venue for this discussion, please let me know and I'll remove it. I appreciate your candor!
It seems like an extremely common pitfall for newcomers to tkinter to do the following:
my_button = tkinter.Button(text='Hello').pack()
only to run into errors because my_button
evaluates to None
after being chained to a geometry manager method, i.e. pack
, place
, or grid
.
Typical practice for this reason is to declare widgets separately from adding them to a geometry manager, e.g.:
my_button = tkinter.Button(text='Hello')
my_button.pack()
A quick look into the source code for the geometry manager classes shows that it would be an extremely trivial change to return the widget on which they're called. The same line can be added to each geometry manager's respective _configure
method: return self
(I have done so below)
pack
class Pack:
"""Geometry manager Pack.
Base class to use the methods pack_* in every widget."""
def pack_configure(self, cnf={}, **kw):
"""Pack a widget in the parent widget. Use as options:
after=widget - pack it after you have packed widget
anchor=NSEW (or subset) - position widget according to
given direction
before=widget - pack it before you will pack widget
expand=bool - expand widget if parent size grows
fill=NONE or X or Y or BOTH - fill widget if widget grows
in=master - use master to contain this widget
in_=master - see 'in' option description
ipadx=amount - add internal padding in x direction
ipady=amount - add internal padding in y direction
padx=amount - add padding in x direction
pady=amount - add padding in y direction
side=TOP or BOTTOM or LEFT or RIGHT - where to add this widget.
"""
self.tk.call(
('pack', 'configure', self._w)
self._options(cnf, kw))
return self # return the widget passed to this method
pack = configure = config = pack_configure
place
class Place:
"""Geometry manager Place.
Base class to use the methods place_* in every widget."""
def place_configure(self, cnf={}, **kw):
"""Place a widget in the parent widget. Use as options:
in=master - master relative to which the widget is placed
in_=master - see 'in' option description
x=amount - locate anchor of this widget at position x of master
y=amount - locate anchor of this widget at position y of master
relx=amount - locate anchor of this widget between 0.0 and 1.0
relative to width of master (1.0 is right edge)
rely=amount - locate anchor of this widget between 0.0 and 1.0
relative to height of master (1.0 is bottom edge)
anchor=NSEW (or subset) - position anchor according to given direction
width=amount - width of this widget in pixel
height=amount - height of this widget in pixel
relwidth=amount - width of this widget between 0.0 and 1.0
relative to width of master (1.0 is the same width
as the master)
relheight=amount - height of this widget between 0.0 and 1.0
relative to height of master (1.0 is the same
height as the master)
bordermode="inside" or "outside" - whether to take border width of
master widget into account
"""
self.tk.call(
('place', 'configure', self._w)
self._options(cnf, kw))
return self # return the widget passed to this method
place = configure = config = place_configure
grid
class Grid:
"""Geometry manager Grid.
Base class to use the methods grid_* in every widget."""
# Thanks to Masazumi Yoshikawa ([email protected])
def grid_configure(self, cnf={}, **kw):
"""Position a widget in the parent widget in a grid. Use as options:
column=number - use cell identified with given column (starting with 0)
columnspan=number - this widget will span several columns
in=master - use master to contain this widget
in_=master - see 'in' option description
ipadx=amount - add internal padding in x direction
ipady=amount - add internal padding in y direction
padx=amount - add padding in x direction
pady=amount - add padding in y direction
row=number - use cell identified with given row (starting with 0)
rowspan=number - this widget will span several rows
sticky=NSEW - if cell is larger on which sides will this
widget stick to the cell boundary
"""
self.tk.call(
('grid', 'configure', self._w)
self._options(cnf, kw))
return self # return the widget passed to this method
grid = configure = config = grid_configure
The crux of my question is: Can anyone explain the design rationale behind this "feature" of tkinter? Ultimately, is a change like this worth a pull request / PEP? Would this cause undue breaking changes to tkinter?
This isn't necessarily a problem so much as it is the result of a deep dive taken after seeing so many questions here regarding this behavior.
CodePudding user response:
Can anyone explain the design rationale behind this "feature" of tkinter?
My guess it's because the underlying methods in the tk library don't return anything. In tcl/tk it's impossible to create and manage the widgets in a single statement so there's just no reason for the pack
, place
, and grid
to return anything. Plus, in the tcl/tk world you can call grid
and pack
and pass in multiple widgets, so it wouldn't make sense to return a single widget.
This is definitely a place where tkinter could stand to be improved IMHO.
Ultimately, is a change like this worth a pull request / PEP?
I personally think so.
Would this cause undue breaking changes to tkinter?
I think it would only cause undue breaking of code that relies on the functions returning None
. I doubt anyone intentionally does that, though there may be some poorly coded programs out there that unintentionally rely on the fact these methods return None
.