Monday, November 12, 2012

lambda default parameters


Making lambdas behave nicely by using default parameters

>>> [c() for c in [(lambda: i) for i in range(10)]]
[9, 9, 9, 9, 9, 9, 9, 9, 9, 9]

>>> [c() for c in [(lambda a=i: a) for i in range(10)]]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

3 comments:

  1. Note to self: This is mostly to solve really basic closure problems without using a wrapper function.

    ReplyDelete
  2. I've just been banging my head over this for an hour. Do you have any idea what the heck is going on?

    ReplyDelete
  3. It becomes clear as you unroll things. First let's convert the lambda to a def, and the list comprehension to a for loop:

    cs = []
    for x in range(10):
    i = x
    def c():
    return i
    [c() for c in cs]

    Now lets flatten out the for loop:

    i = 1
    def a1():
    return i
    i = 2
    def a2():
    return i
    i = 3
    def a3():
    return i
    #...
    [c() for c in [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10]

    Hopefully now it is obvious: the function is returning the value of the variable i at the time it is called. What the function is told to return is the VARIABLE i, not it's value at the time of function definition.

    The disassembler may also be helpful here:

    >>> import dis
    >>> dis.dis(lambda: i)
    1 0 LOAD_GLOBAL 0 (i)
    3 RETURN_VALUE

    Note, there is no reference to a value anywhere. At the time the lambda is executed, it will load the variable i.

    The default parameter on the other hand catches the current value of the variable. Default parameters are only evaluated once.

    The flip side of the correctness of the default parameter (lambda a=i: a) is that it has the "gotcha" of mutable types. Everyone gets bit by def foo(bar=[]) at some point. :-)

    ReplyDelete