Posts by author ygravrand

Nagare Callbacks, Iterations And The Mighty Default Parameter

Problem

Consider the following Python code:

>>> funcs = []
>>> for a in [1,2,3]:
...     funcs.append(lambda:a)
...
>>> print [f() for f in funcs]
[3, 3, 3]

Here, I created three anonymous functions, which return the value of a. Intuitively, we could think that when evaluating these functions, they would return the value a had when it was referenced during the function creation. Instead, all three functions return the last value of a. Why ?

The lambda expression captured a reference to the outside-defined scope that holds the variable a. This reference is used when evaluating the lambda expression. Normally, a wouldn't exist outside the loop, but lambda expressions captured a reference to it. Too bad, it evaluates to the last value it had.

How can we solve this problem ?

In Python, "Default parameter values are evaluated when the function definition is "executed" (see Python reference documentation).

We can use this behaviour to freeze the value of the iteration variable in our lambda expression.

Example:

>>> funcs = []
>>> for a in [1,2,3]:
...     funcs.append(lambda a=a:a)
...
>>> print [f() for f in funcs]
[1, 2, 3]

Application to Nagare

In Nagare, you often create a lambda expression as a callback to an action. For example:

h << h.input(type='submit', value='Cancel').action(lambda: comp.answer())

Here, comp is referenced in the lambda expression. It's ok because comp is not an iteration variable.

But let's take a look at the main view of the "Tic Tac Toe" example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@presentation.render_for(TicTacToe)
def default_view(self, h, comp, *args):
    with h.table:
        i = 0
        for row in zip(self._board[::3], self._board[1::3], self._board[2::3]):
            with h.tr:
                for cell in row:
                    with h.td:
                        if cell == 0:
                            h << h.a('-').action(lambda i=i: comp.answer(i))
                        else:
                            h << ('X' if cell==1 else 'O')
                        i += 1
    return h.root

In this view, there is an iteration to create the board, and for each link, we would like the associated callback to answer the value of i, i.e. the id of the clicked cell! To achieve that, you can see that we put a default argument value on the lambda (line 10).

So remember to use the Mighty Default Parameter when defining Callbacks in an Iteration!