views:

778

answers:

2

I've created a method that generates a new class and adds some methods into the class, but there is a strange bug, and I'm not sure what's happening:

def make_image_form(image_fields):
    ''' Takes a list of image_fields to generate images '''
    images = SortedDict()
    for image_name in image_fields:
        images[image_name] = forms.ImageField(required=False)
    new_form = type('ListingImagesForm2', (forms.BaseForm,), {'base_fields' : images})
    #now we add the validation methods to the class
    for image_name in image_fields:
       print "image name is: ", image_name
       setattr(new_form, 'clean_' + image_name, lambda self: self._clean_photo(image_name))
    #Add the _clean_photo method to the class
    setattr(new_form, '_clean_photo', _clean_photo)
    return new_form

This is my method, which takes a list of image_fields (I'm making a site in Django), and it creates a whole bunch of ImageField fields, and creates a class ListingImagesForm2, and assigns the image fields to the class.

The problem is in creating the methods, and more specifically the method content.

In the loop:

for image_name in image_fields:
    print "image name is: ", image_name
    setattr(new_form, 'clean_' + image_name, lambda self: self._clean_photo(image_name))

The methods signatures are created correctly (e.g. clean_pic_1, clean_pic_2...) , but I think there is a problem in the lambda expression, as the _clean_photo method is always called with the same image name (which happens to be the last image name in the image_fields list).

Is there any nicer way to create dynamic method content(code), than using lambda expressions?

And why would my lambda expression only pass _clean_photo the last image_name in the for loop?

+3  A: 

Python code behaves like this for functions defined in scope of methods. Use this instead:

for image_name in image_fields:
    print "image name is: ", image_name
    setattr(new_form, 'clean_' + image_name, 
            lambda self, iname=image_name: self._clean_photo(iname))

The usage of default keyword argument makes Python remember it at the time of lambda function creation rather than at the time of its calling (when it would always take the last image).

DzinX
A: 

Thanks, works perfectly! I think I have quite a bit of python still to learn :)

Louis Sayers
Glad to have helped :) Also remember that on StackOverflow we write thanks and such in comments, not as answers to the question :)
DzinX