0

For my application GUI (tkinter), I need a 'bookkeeping' variable that holds the current row number for a UI widget. I prefer not to hard code the row number because inserting a widget would involve renumbering all row numbers following it. In this situation, an increment operator (x++, similar to C++) would come in very handy, because incrementing the variable when necessary would require only two additional characters. However, Python does not support increment operators.

To avoid increment statement between elements that should go on different rows, I came up with the following solution but I am wondering what an alternative might be:

rownums = list(range(100))
rowstay = lambda: rownums[0]        # Return current row number and stay on row.
rowmove = lambda: rownums.pop(0)    # Return current row number and move on to next.

print(f'Two items: {rowstay()}, {rowmove()}')
print(f'One item: {rowmove()}')
print(f'Two items: {rowstay()}, {rowmove()}')

For example (tkinter):

# Create some buttons.
mybutton1 = tkinter.button(master, 'Button on row 0')
mybutton2 = tkinter.button(master, 'Button on row 0')
mybutton3 = tkinter.button(master, 'Button on row 1')


# Place buttons (use mimicked increment 'operator').
mybutton1.grid(row=rowstay(), column=0)
mybutton2.grid(row=rowmove(), column=1)
mybutton3.grid(row=rowmove(), column=0)

What alternative code would provide the same functionality?

  • 3
    Python has `+= 1` which is equivalent to `++`. – Barmar Jan 30 '20 at 21:51
  • 1
    Having lambdas with hard-coded variables seems very unclean. You'll need to repeat this for every variable you want to be able to increment. Either define functions that take parameters or define a class. – Barmar Jan 30 '20 at 21:55
  • To add to what @Barmar said, you can also define a lambda function that takes an input: `lambda x: rownums.pop(x)`. – felipe Jan 30 '20 at 21:55
  • If you define a class, you can define the `__iadd__` method, which implements the `+=` operator, and then you can write `rownums += 1` – Barmar Jan 30 '20 at 21:57
  • 1
    This is a very weird question. Having the equal signs and dots line up is not important. Also, assigning `lamba`s to variables is [unpythonic](https://www.python.org/dev/peps/pep-0008/#programming-recommendations) – James Jan 30 '20 at 21:57
  • 1
    Anyway, this question isn't really appropriate for SO. It's a question about programming style, which is a matter of opinion. [softwareengineering.se] or [codereview.se] might be more appropriate. – Barmar Jan 30 '20 at 21:59
  • What's weird about it? According to PEP8 [https://www.python.org/dev/peps/pep-0008/] vertical alignment for increased legibility is a serious consideration. When I am positioning widgets, it is very helpful to have row and column arguments aligned. – Jan-Willem Lankhaar Jan 30 '20 at 22:02
  • @Jan-WillemL well, the thing that is against PEP8 is assigning a lambda to a name. Don't do that. – juanpa.arrivillaga Jan 30 '20 at 22:23
  • Does this answer your question? [Incrementing a counter while assigning it in python](https://stackoverflow.com/questions/60022643/incrementing-a-counter-while-assigning-it-in-python) – kaya3 Feb 02 '20 at 15:01
  • Actually, it does, but as pointed in the answer, it is not the recommended way. – Jan-Willem Lankhaar Feb 03 '20 at 06:08

1 Answers1

0

From the comments and answer, I learned that mutating with a lambda and assigning lambdas is not Pythonic. Several comments suggest using += 1 but this is exactly what I wanted to avoid because it requires an additional statement for each time the variable should be incremented. The solution with higher order functions (or a class) mimics ++x instead of x++.

My solution (second-best, see better solution under EDIT):

class BookkeepingVar():
    """ Variable with the ++-operator from C++ mimicked.

        Example:
        x = BookkeepingVar(0)
        y = x() + 1    # y = 1, x = 1 
        z = x(1) + 1   # z = 1, x = 2 
    """

    def __init__(self, start=0):
        self.var = [start]

    def __call__(self, incr=0):
        if incr:
            self.var.append(self.var[0] + incr)
            return self.var.pop(0)
        else:
            return self.var[0]

Example usage:

# Create some buttons.
mybutton1 = tkinter.button(master, 'Button on row 0')
mybutton2 = tkinter.button(master, 'Button on row 0')
mybutton3 = tkinter.button(master, 'Button on row 1')

# Place buttons.
row = BookkeepingVar(0)
mybutton1.grid(row=row(0), column=0)
mybutton2.grid(row=row(1), column=1)
mybutton3.grid(row=row(1), column=0)

Please note:

  1. There's no upper bound to the variable value (as in the lambda example).
  2. The list length never exceeds two, so I can't imagine inefficiencies of pop() to be problematic.
  3. The solution is slightly more general than mimicking x++ only because it also supports increments other than 1.
  4. The solution also supports decrements (use a negative argument).
  5. The __call__ method was implemented in order to make notation as short as possible.

EDIT:

A simpler and more elegant solution without list (same usage):

class BookkeepingVar():
    """ Variable with the ++-operator from C++ mimicked.

        Example:
        x = BookkeepingVar(0)
        y = x() + 1    # y = 1, x = 1 
        z = x(1) + 1   # z = 1, x = 2 
    """
    def __init__(self, start=0):
        self.var = start

    def __call__(self, incr=0):
        self.var += incr
        return self.var - incr

EDIT 2:
Remark about ++x vs. x++ (strikethrough).

  • Given you apparently don't use things like rowspan, I'd have the custom object be in charge of the entire thing and call `grid` internally e.g. `layout = Layout(); layout.row(mybutton1, mybutton2); layout.row(mybutton3)`. And `layout` could keep track of the current row using either a regular integer or an `itertools.count()`. Obviously if you're using a more complex layout than just a bunch of rows that doesn't work. Though you could probably have something even more declarative. – Masklinn Jan 31 '20 at 10:44