lambda trouble

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Darabos Daniel

    lambda trouble

    Hi!

    I was doing something like this:
    [color=blue][color=green][color=darkred]
    >>> def p( x ):[/color][/color][/color]
    .... print x
    ....[color=blue][color=green][color=darkred]
    >>> l = []
    >>> for i in range( 5 ):[/color][/color][/color]
    .... l.append( lambda: p( i ) )
    ....[color=blue][color=green][color=darkred]
    >>> for k in l:[/color][/color][/color]
    .... k()
    ....
    4
    4
    4
    4
    4

    And it surprised me a little. I was expecting to see 0, 1, 2, 3, 4.
    After some brainwork I now kind of understand what happens and I even
    found a solution like this:
    [color=blue][color=green][color=darkred]
    >>> def mylambda( fn, *args ):[/color][/color][/color]
    .... return lambda: apply( fn, args )
    ....[color=blue][color=green][color=darkred]
    >>> l = []
    >>> for i in range( 5 ):[/color][/color][/color]
    .... l.append( mylambda( p, i ) )
    ....[color=blue][color=green][color=darkred]
    >>> for k in l:[/color][/color][/color]
    .... k()
    ....
    0
    1
    2
    3
    4

    But I still feel a bit unsatisfied. Do you have some advice for me?

    Cheers,

    Daniel


  • Gandalf

    #2
    Re: lambda trouble


    Darabos Daniel wrote:
    [color=blue]
    >And it surprised me a little. I was expecting to see 0, 1, 2, 3, 4.
    >After some brainwork I now kind of understand what happens and I even
    >found a solution like this:
    >[/color]
    Well, it is not suprising. In a lambda expression, everything after the
    : is symbolic. It is compiled,
    not evaluated. You can use a named function instead of an unnamed one:

    [GCC 3.3.3 [FreeBSD] 20031106] on freebsd5
    Type "help", "copyright" , "credits" or "license" for more information.[color=blue][color=green][color=darkred]
    >>> def p(x):[/color][/color][/color]
    .... print x
    ....[color=blue][color=green][color=darkred]
    >>> l = []
    >>> for i in range( 5 ):[/color][/color][/color]
    .... def func():
    .... p(i)
    .... l.append( func )
    ....[color=blue][color=green][color=darkred]
    >>> for k in l:[/color][/color][/color]
    .... k()
    ....[color=blue][color=green][color=darkred]
    >>> l[/color][/color][/color]
    [<function func at 0x81a4f0c>, <function func at 0x81a4f44>, <function
    func at 0x81a4f7c>, <function func at 0x81a4fb4>, <function func at
    0x81ae02c>][color=blue][color=green][color=darkred]
    >>>[/color][/color][/color]

    You have different functions. However, in every function, the name 'i'
    is not a local name. (Python has only two levels: local and global.)
    Here is another solution. However, it uses eval. Probably, it is not the
    fastest one.

    def p(x):
    print x

    l = []
    for i in range( 5 ):
    l.append(eval(" lambda: p( %s )"%i))

    for k in l:
    k()


    Udv,

    Laci


    Comment

    • Bruce Wolk

      #3
      Re: lambda trouble

      Gandalf wrote:[color=blue]
      >
      > Darabos Daniel wrote:
      >[color=green]
      >> And it surprised me a little. I was expecting to see 0, 1, 2, 3, 4.
      >> After some brainwork I now kind of understand what happens and I even
      >> found a solution like this:
      >>[/color]
      > Well, it is not suprising. In a lambda expression, everything after the
      > : is symbolic. It is compiled,
      > not evaluated. You can use a named function instead of an unnamed one:
      >
      > [GCC 3.3.3 [FreeBSD] 20031106] on freebsd5
      > Type "help", "copyright" , "credits" or "license" for more information.[color=green][color=darkred]
      > >>> def p(x):[/color][/color]
      > ... print x
      > ...[color=green][color=darkred]
      > >>> l = []
      > >>> for i in range( 5 ):[/color][/color]
      > ... def func():
      > ... p(i)
      > ... l.append( func )
      > ...[color=green][color=darkred]
      > >>> for k in l:[/color][/color]
      > ... k()
      > ...[color=green][color=darkred]
      > >>> l[/color][/color]
      > [<function func at 0x81a4f0c>, <function func at 0x81a4f44>, <function
      > func at 0x81a4f7c>, <function func at 0x81a4fb4>, <function func at
      > 0x81ae02c>][color=green][color=darkred]
      > >>>[/color][/color]
      >
      > You have different functions. However, in every function, the name 'i'
      > is not a local name. (Python has only two levels: local and global.)
      > Here is another solution. However, it uses eval. Probably, it is not the
      > fastest one.
      >
      > def p(x):
      > print x
      >
      > l = []
      > for i in range( 5 ):
      > l.append(eval(" lambda: p( %s )"%i))
      >
      > for k in l:
      > k()
      >
      >
      > Udv,
      >
      > Laci
      >
      >[/color]
      It is really even simpler:
      [color=blue][color=green][color=darkred]
      >>> def p(x):[/color][/color][/color]
      print x
      [color=blue][color=green][color=darkred]
      >>> l=[lambda i=i: p(i) for i in range(5)]
      >>> for k in l:[/color][/color][/color]
      k()

      0
      1
      2
      3
      4[color=blue][color=green][color=darkred]
      >>>[/color][/color][/color]

      Comment

      • Terry Reedy

        #4
        Re: lambda trouble


        "Darabos Daniel" <cyhawk@sch.bme .hu> wrote in message
        news:Pine.GSO.4 .58L0.040319173 6410.14978@balu ...[color=blue]
        > Hi!
        >
        > I was doing something like this:
        >[color=green][color=darkred]
        > >>> def p( x ):[/color][/color]
        > ... print x
        > ...[color=green][color=darkred]
        > >>> l = []
        > >>> for i in range( 5 ):[/color][/color]
        > ... l.append( lambda: p( i ) )
        > ...[color=green][color=darkred]
        > >>> for k in l:[/color][/color]
        > ... k()
        > ...
        > 4
        > 4
        > 4
        > 4
        > 4
        >
        > And it surprised me a little. I was expecting to see 0, 1, 2, 3, 4.[/color]

        If you had written

        for i in range(5):
        def f(): return p(i)
        l.append(f)

        would you have still been surprised? Or given that f is constant, how
        about the exactly equivalent

        def f(): return p(i)
        for i in range(5):
        l.append(f)

        or even the equivalent

        def f(): return p(j)
        for i in range(5):
        l.append(f)
        j=i
        def p(z): print z
        <etc>

        [color=blue]
        > After some brainwork I now kind of understand what happens and I even
        > found a solution like this:
        >[color=green][color=darkred]
        > >>> def mylambda( fn, *args ):[/color][/color]
        > ... return lambda: apply( fn, args )
        > ...[color=green][color=darkred]
        > >>> l = []
        > >>> for i in range( 5 ):[/color][/color]
        > ... l.append( mylambda( p, i ) )
        > ...[color=green][color=darkred]
        > >>> for k in l:[/color][/color]
        > ... k()
        > ...
        > 0
        > 1
        > 2
        > 3
        > 4
        >
        > But I still feel a bit unsatisfied. Do you have some advice for me?[/color]

        Remember that 1) 'lambda args: expr' basically abbreviates 'def f(args):
        return expr'; and 2) function code bodies only execute when the function
        is called, and in particular, do not dereference or access globals until
        called. While a function is being constructed, it is irrelevant that a
        global even exist, let along that it be in use as a loop var.

        Terry J. Reedy




        Comment

        • Karl Pflästerer

          #5
          Re: lambda trouble

          On 19 Mar 2004, Darabos Daniel <- cyhawk@sch.bme. hu wrote:
          [color=blue][color=green][color=darkred]
          >>>> def p( x ):[/color][/color]
          > ... print x
          > ...[color=green][color=darkred]
          >>>> l = []
          >>>> for i in range( 5 ):[/color][/color]
          > ... l.append( lambda: p( i ) )
          > ...[color=green][color=darkred]
          >>>> for k in l:[/color][/color]
          > ... k()
          > ...
          > 4
          > 4
          > 4
          > 4
          > 4[/color]
          [color=blue]
          > And it surprised me a little. I was expecting to see 0, 1, 2, 3, 4.[/color]

          The `i' in your code is bound to the global value of `i' which is 4.
          [color=blue]
          > After some brainwork I now kind of understand what happens and I even
          > found a solution like this:[/color]
          [color=blue][color=green][color=darkred]
          >>>> def mylambda( fn, *args ):[/color][/color]
          > ... return lambda: apply( fn, args )[/color]
          [...]
          [color=blue]
          > But I still feel a bit unsatisfied. Do you have some advice for me?[/color]

          You have to bind the value of `i' in the lambda form; you did that with
          your function definition but it can be done a bit simpler:
          [color=blue][color=green][color=darkred]
          >>> funs = [lambda i=i: sys.stdout.writ e(str(i)+"\n") for i in range(4)]
          >>> for f in funs:[/color][/color][/color]
          .... f()
          ....
          0
          1
          2
          3[color=blue][color=green][color=darkred]
          >>>[/color][/color][/color]

          With the `i=i' the `i' in the lambda got bound.


          KP

          --
          And has thou slain the Jabberwock?
          Come to my arms, my beamish boy!
          O frabjous day! Callooh! Callay!'
          He chortled in his joy. "Lewis Carroll" "Jabberwock y"

          Comment

          • Daniel Yoo

            #6
            Re: lambda trouble

            Darabos Daniel <cyhawk@sch.bme .hu> wrote:

            :>>> def p( x ):
            : ... print x
            : ...
            :>>> l = []
            :>>> for i in range( 5 ):
            : ... l.append( lambda: p( i ) )
            : ...
            :>>> for k in l:
            : ... k()
            : ...
            : 4
            : 4
            : 4
            : 4
            : 4

            : And it surprised me a little. I was expecting to see 0, 1, 2, 3, 4.


            Hi Darabos,


            Someone should make a FAQ out of this. *grin*


            This keeps popping up every so often on comp.lang.pytho n. Here's a
            link to one of the really heated threads about this:




            It sounds like you may have some experience with some kind of
            functional language like Scheme. If so, then you probably understand
            the distinction between DEFINE and SET! in Scheme.


            The issue that you're running into is that Python uses a SET!-like
            behavior when it reassigns the index variable of loops. To see this
            more easily, let me translate your program into an equivalent Scheme
            program:

            ;;;
            guile> (define (p x) (display x) (newline))
            guile> (define l '())
            guile> (define i 0)
            guile> (while (< i 5)
            (begin
            (set! l (cons (lambda () (p i)) l))
            (set! i (+ i 1))))
            guile> (for-each (lambda (x) (x)) l)
            5
            5
            5
            5
            5
            ;;;

            So here we can see the same problem popping up.



            : found a solution like this:
            :>>> def mylambda( fn, *args ):
            : ... return lambda: apply( fn, args )
            : ...
            :>>> l = []
            :>>> for i in range( 5 ):
            : ... l.append( mylambda( p, i ) )
            : ...


            Yup, this works, because we take advantage of the name-binding
            behavior of parameter passing. Another way of saying the same thing
            is:

            ###[color=blue][color=green][color=darkred]
            >>> def make_p(x):[/color][/color][/color]
            .... def p():
            .... print x
            .... return p
            ....[color=blue][color=green][color=darkred]
            >>> l = []
            >>> for i in range(5):[/color][/color][/color]
            .... l.append(make_p (i))
            ....[color=blue][color=green][color=darkred]
            >>> for k in l:[/color][/color][/color]
            .... k()
            ....
            0
            1
            2
            3
            4
            ###


            It's the issue of DEFINE vs. SET! --- the first assignment in Python
            acts like DEFINE, and subsequence reassignments act like SET!. So
            just one thing to be careful about with assignment.


            I hope this helps!

            Comment

            • Michele Simionato

              #7
              Re: lambda trouble

              Daniel Yoo <dyoo@hkn.eecs. berkeley.edu> wrote in message news:<c3g1c0$ui m$1@agate.berke ley.edu>...[color=blue]
              >
              > Someone should make a FAQ out of this. *grin*[/color]

              Just to know, how does it work? If I write a FAQ entry, who is in
              charge
              of putting it in the official FAQ? (I don't have the time to write
              down
              this specific FAQ now, but I may be willing to write down a FAQ in the
              future).
              [color=blue]
              > This keeps popping up every so often on comp.lang.pytho n. Here's a
              > link to one of the really heated threads about this:
              >
              > http://groups.google.com/groups?hl=e...0119375&rnum=6
              >[/color]

              Here is a non-trollish thread on the subject, which may also
              be interesting if you know some Lisp/Scheme:



              HTH,

              Michele Simionato

              Comment

              • Elaine Jackson

                #8
                Re: lambda trouble

                The problem, they say, is that a variable in a FOR clause can't bind a variable
                inside a lambda abstraction. Fortunately the lambda operator itself doesn't
                share this limitation, so you can make the variable visible to the binder in the
                FOR clause by binding with a lambda and then evaluating the resulting
                abstraction at the variable in question, like so:
                [color=blue][color=green][color=darkred]
                >>> def p(x): print x
                >>> list=[]
                >>> for i in range(5): list.append((la mbda j: lambda: p(j))(i))
                >>> x=map(lambda f: f(),list)[/color][/color][/color]
                0
                1
                2
                3
                4

                Or, more concisely,
                [color=blue][color=green][color=darkred]
                >>> def p(x): print x
                >>> list=[(lambda j: lambda: p(j))(i) for i in range(5)]
                >>> x=map(lambda f: f(),list)[/color][/color][/color]
                0
                1
                2
                3
                4


                Comment

                • Michele Simionato

                  #9
                  Re: lambda trouble

                  "Elaine Jackson" <elainejackson7 355@home.com> wrote in message news:<b397c.868 228$X%5.57128@p d7tw2no>...[color=blue]
                  > The problem, they say, is that a variable in a FOR clause can't bind a variable
                  > inside a lambda abstraction.[/color]

                  I find this explanation confusing. It is just a matter of times:
                  the lambda uses the value of the bound name at *calling* time, not at
                  *definition* time:
                  [color=blue][color=green][color=darkred]
                  >>> i=1
                  >>> f=lambda : i # definition time, i=1
                  >>> i=2
                  >>> f() # calling time, i has been rebound to 2[/color][/color][/color]
                  2
                  [color=blue][color=green][color=darkred]
                  > >>> def p(x): print x
                  > >>> list=[(lambda j: lambda: p(j))(i) for i in range(5)]
                  > >>> x=map(lambda f: f(),list)[/color][/color][/color]

                  This works since the inner lambda uses the value of j at the calling time
                  of the outer lambda.

                  Just to clarifify the point for the OP, since I am sure you understand
                  the all story ;)


                  Michele Simionato

                  Comment

                  • Jacek Generowicz

                    #10
                    Re: lambda trouble

                    Darabos Daniel <cyhawk@sch.bme .hu> writes:
                    [color=blue]
                    > Do you have some advice for me?[/color]

                    I'll write the concise outline in words here. If you want a more
                    detailed explanation, then you could do worse than reading the thread
                    suggested by Michele. Therein he and I go into quite some
                    (excruciating and graphic :-) detail of what is going on.

                    The original problem is that there is a single binding of "i" which is
                    shared by all the lambdas you shove into "l". You want each lambda to
                    have its own, separate, binding of "i". To do this, you need to create
                    a new scope which binds "i" (originally, all the lambdas find "i" in
                    an outer scope). In Python, the only way to create a new nested scope
                    is with functions (which includes lambdas); any local variables in a
                    function are bound in a new inner scope. You sholuld create an i which
                    is local to your function (lambda), and bind it to the value you want.

                    For example:

                    lambda i=i: p(i)

                    Notes:

                    "i=i" means "i is a parameter (hence also a local variable) whose
                    default value is 'i at the time of function creation'" ...

                    .... therefore, the lambda now has its own "i", which is bound to the
                    value of the outer "i" at the time that the lambda form is being
                    evaluated.

                    Comment

                    Working...