0

Windows 7x64 Python2.7

I'm looking to apply the Lexical closures in Python thread solution to my code. Additional readings Closures in a for loop and lexical environment

I go through a list of image, thumbnail pairs, and display the thumbnail, and upon clicking the label, it should show a new TopLevel with the full sized image in it.

What actually happens is that it is only the last image of the ImagePairs is shown. Searching around, I found that thread I posted above, but I'm uncertain about how exactly to apply it to my situation.

            row, col = 0, 0
            #create a frame for the row
            rowFrame = Frame(master)
            for image, thumb in ImagePairs:
                curLbl = Label(rowFrame, image=thumb)
                curLbl.grid(row=0, column=col, sticky='news')

                curLbl.bind('<Button-1>', lambda e:self.popImage(image))  #or curLbl.image

                col += 1
                if col >= 3:
                    rowFrame.grid(row=row)
                    #create a new frame, for the next row
                    rowFrame = Frame(master)
                    col = 0
                    row += 1

I figured making a function like

def func(img=img):
   return img

and insert it in the space between grid() and bind(), but then I get the error for a missing image. How can I modify my code to fit that in?

Community
  • 1
  • 1
TankorSmash
  • 12,186
  • 6
  • 68
  • 106
  • Look at the existing `lambda`: `lambda e:self.popImage(image)`. What is the part of this that changes each time through the loop? `image`, right? So, that's what needs to be bound early, using the technique in one of the linked questions. – Karl Knechtel Aug 19 '22 at 11:52

1 Answers1

4

You can add the default value image = image to the lambda itself:

for image, thumb in ImagePairs:
            curLbl = Label(rowFrame, image=thumb)
            curLbl.grid(row=0, column=col, sticky='news')
            curLbl.bind('<Button-1>', lambda e, image = image:self.popImage(image))  #or curLbl.image

Your idea of defining func would also work if you made the default value image instead of img:

def func(e, img = image):
    self.popImage(img)

The key thing to remember is that default values are bound at the time the lambda (or function) is defined.

So each time through the loop, a new lambda is defined with its own local variable image, with its own default value for image bound at definition-time. Thus, when the lambda is called later, the right image is accessed.

Contrast that with the situation when no default value is supplied: image is no longer a local variable of the lambda. When the lambda is called, Python goes looking for it in the enclosing scope. But by then the for-loop is done and image in the enclosing scope is set to the last value in ImagePairs. That is why you were getting the same last image no matter what button you pressed: all the lambdas were accessing the value of the same variable image.

unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • Well, thank you, that worked great, a lot less messy than what I was trying to do! I don't quite understand what it does though... Does it take the image from the list and assign it the name 'image' in the lambdas scope? Then it feeds the newly created image value to the popImage function when it is called? Thanks again! – TankorSmash Apr 13 '12 at 21:02
  • 1
    re:edit Thank you for making that even more clear. `lambda e, NEW=image: self.popImage(NEW))` is what's happening. Thanks. – TankorSmash Apr 13 '12 at 21:29
  • Yes, exactly. Using a different variable name does clarify what is going on. – unutbu Apr 13 '12 at 21:40