This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: Place, Pack and Grid should return the widget
Type: enhancement Stage: resolved
Components: Tkinter Versions: Python 3.8
process
Status: closed Resolution: rejected
Dependencies: Superseder:
Assigned To: Nosy List: Epyxoid, gpolo, josh.r, serhiy.storchaka, terry.reedy, zach.ware
Priority: normal Keywords:

Created on 2019-01-09 13:31 by Epyxoid, last changed 2022-04-11 14:59 by admin. This issue is now closed.

Pull Requests
URL Status Linked Edit
PR 11475 closed Epyxoid, 2019-01-09 13:31
Messages (7)
msg333318 - (view) Author: Peter Vex (Epyxoid) * Date: 2019-01-09 13:31
When you want to simply place a widget on a window and you also want to store the reference for that widget in a variable you can't do that in one line, which is really unpleasant, because when you create a new widget these things are usually the first what you want to do with a widget and breaking it two line is just making things more complicated.

For example, if you want to create 3 label, place it next to each other and store their reference:

import tkinter as tk
root = tk.Tk()

# you can't do that:
# here the variables assigned to None, since grid() returns 'nothing'
label1 = tk.Label(root).grid(row=0, column=0)
label2 = tk.Label(root).grid(row=0, column=1)
label3 = tk.Label(root).grid(row=0, column=2)

# actually, you must do this:
label1 = tk.Label(root)
label1.grid(row=0, column=0)
label2 = tk.Label(root)
label2.grid(row=0, column=1)
label3 = tk.Label(root)
label3.grid(row=0, column=2)
msg333323 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2019-01-09 14:51
Methods place(), pack() and grid() correspond to corresponding Tk commands. These commands return nothing (empty string) in Tk, and Tkinter methods return nothing (None) in Python. If they will became returning something meaningful in future versions of Tk, we will lost a chance to translate this to Python if make them now returning the widget itself.

Note also that method chaining is not commonly used in Python. This code would look pretty unpythonic.

For these reasons I am against this idea.
msg333324 - (view) Author: Peter Vex (Epyxoid) * Date: 2019-01-09 15:19
I can somewhat agreed with your point, even if it's not too realistic, but reasonable enough.

Also, I'd only use method chaining, because that'd be the only option. I can't pass an argument to tk.Label at creation like 'manager="grid"' or anything like that. In any case, I think doing it in a separate line makes the code longer and less readable even if it's pythonic.

Thanks for your consideration.
msg333332 - (view) Author: Zachary Ware (zach.ware) * (Python committer) Date: 2019-01-09 16:51
I agree with Serhiy that we shouldn't do this; tkinter is (mostly) just a thin wrapper around Tcl/Tk and this isn't enough of a win to deviate from that.  In any case, this would be an enhancement that could only go into 3.8 at this point.

For your particular example, it would prefer to read the following anyway:

```
import tkinter as tk

root = tk.Tk()

labels = []
for i in range(3):
    label = tk.Label(root)
    label.grid(row=0, column=i)
    labels.append(label)
```


Or if you really want this feature in your own code, try this out:

```
import tkinter as tk
from functools import partial

def _mapped(method, widget, *args, **kwargs):
    getattr(widget, method)(*args, **kwargs)
    return widget

packed = partial(_mapped, 'pack')
gridded = partial(_mapped, 'grid')
placed = partial(_mapped, 'place')

root = tk.Tk()

label1 = gridded(tk.Label(root), row=0, column=0)
label2 = gridded(tk.Label(root), row=0, column=1)
label3 = gridded(tk.Label(root), row=0, column=2)
```
msg333337 - (view) Author: Peter Vex (Epyxoid) * Date: 2019-01-09 17:19
Thank you Zachary, very interesting examples, to say the least!
msg333339 - (view) Author: Josh Rosenberg (josh.r) * (Python triager) Date: 2019-01-09 17:47
Closing as rejected; to my knowledge, *no* built-in Python method both mutate an object and returns the object just mutated, precisely because:

1. It allows for chaining that leads fairly quickly to unreadable code (Python is not Perl/Ruby)

2. It creates doubt as to whether the original object was mutated or not (if list.sort returns a sorted list, it becomes unclear as to whether the original list was sorted as well, or whether a new list was returned; sortedlist = unsortedlist.sort() might give an inaccurate impression of what was going on). Zachary's example of using top-level functions to do the work instead is basically the same practicality compromise that sorted makes in relation to list.sort.
msg333343 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2019-01-09 19:20
1. (Josh beat me here.)  One of Python's design features is that stdlib functions/methods that mutate (or register) an object never (as far as I can remember) return the object.  (If they return anything other than None, it is something other than the object.  List.pop returns a member, not the list.)  Changing this rule either in particular cases or in general has been rejected by Guido multiple times.  A new proposal for particular exceptions would have to be approved by the still-in-the-future new design decision process.  It would likely start with posting to python-ideas list.

Consistency of this rule is a feature.  If people got used to writing 'item = Widget(...).geo_manager(...)', they would more often make the mistake of writing buggy code like 'items = lister().sort()'


2. (Based on experience with IDLE.)  The toy example with 3 one-line statements creating 3 blank Labels admittedly looks nice and neat.  And in such situations, can actually get close today.

label1 = tk.Label(root); label1.grid(row=0, column=0)
label2 = tk.Label(root); label2.grid(row=0, column=1)
label3 = tk.Label(root); label3.grid(row=0, column=2)

However, in real situations, the widget creation statement is often or usually in the body of a class method and starts with at least an 8 space indent.  The call nearly always has a content argument and often other configuration arguments.  Such statements often require more than one line even without the manager call added.  Manager calls may also have additional arguments.  The result is ragged code and if manager calls are appended, they are not so easy to pick out.

I believe a majority of experienced tkinter users prefer a style omitted from the opening example: create a batch of widgets; then lay them out.  The following untested example shows the idea.  It keeps the row and column numbers close together and includes parent grid calls in the layout section.

class Viewframe(Frame):
    def __init__(self, parent, ...)
        ...
        self.populate()
    def populate(self):
        label = Label(self, text='hello world', ...)
        button = Button(self, text='press me), command=lambda: <do something>)
        text = Text(self, ...)

        label.grid(row=0, column=0)
        button.grid(row=1, column=0)
        text.grid(row=0, column=1, rowspan=2, sticky='NSEW')
        self.columnconfigure(column=1, weight=1)
History
Date User Action Args
2022-04-11 14:59:10adminsetgithub: 79881
2019-01-09 19:20:42terry.reedysetmessages: + msg333343
2019-01-09 17:47:24josh.rsetstatus: open -> closed

nosy: + josh.r
messages: + msg333339

resolution: rejected
stage: resolved
2019-01-09 17:21:06zach.waresettype: behavior -> enhancement
versions: + Python 3.8, - Python 3.7
2019-01-09 17:19:07Epyxoidsettype: enhancement -> behavior
messages: + msg333337
versions: + Python 3.7, - Python 3.8
2019-01-09 16:51:59zach.waresetversions: + Python 3.8, - Python 3.7
nosy: + zach.ware

messages: + msg333332

type: behavior -> enhancement
2019-01-09 15:19:01Epyxoidsetmessages: + msg333324
2019-01-09 14:51:35serhiy.storchakasetnosy: + terry.reedy, gpolo, serhiy.storchaka
messages: + msg333323
2019-01-09 13:31:37Epyxoidcreate