1

I am trying to dynamically create some methods to a class but when I create an object of this class and access the properties, I always get the last value.

properties = ['a', 'b', 'c']
for p in properties:
    f = property(lambda self: p)
    print p # you see a, b, c
    print f # you can see it is 3 different objects
    setattr(MyClass, p, f)

obj = MyClass()
obj.a # returns c !!! why?
obj.b # returns c !!! why?
obj.c # returns c

UPDATE

I was able to make it work using a function to create the method:

def create_function(p):    
    def f(self,):
        return p   
    return f
Michael
  • 8,357
  • 20
  • 58
  • 86

1 Answers1

4

This has nothing to do with using property. It's explained in the official FAQ as Why do lambdas defined in a loop with different values all return the same result?.

When you do this:

properties = ['a', 'b', 'c']
for p in properties:
    f = property(lambda self: p)
    print p # you see a, b, c
    print f # you can see it is 3 different objects
    setattr(MyClass, p, f)

… each f is a closure over the p variable.* This means that, when you call that f, it will return the current value of p within its defining scope, not the value of p at the time at which the closure was constructed. At the end of the loop, p's value is 'c', so that's what all of your properties return.

You can get around this by (among other solutions**) using the "default value hack":

properties = ['a', 'b', 'c']
for p in properties:
    f = property(lambda self, p=p: p)
    print p # you see a, b, c
    print f # you can see it is 3 different objects
    setattr(MyClass, p, f)

Now, each property has a parameter named p, whose default value is the value of p at function definition time. When called (without supplying a p argument), the body ends up with a local variable p with that value, instead of a closure variable p referring to the non-local scope.


* Actually, it's not technically a closure, because the defining scope is global. But it acts the same as if it were done inside another scope.

** There's also the JavaScript idiom of creating a new scope to define the function within, (lambda p: lambda self: p)(p), or functools.partial, or creating a callable object instead of a function, or… But, despite having the word "hack" in the name, the "default value hack" is a well-known Python idiom, enshrined in the docs and used within the stdlib, and it's usually the best answer, unless you have some other reason for another scope or a partial or a custom callable.

abarnert
  • 354,177
  • 51
  • 601
  • 671
  • I see, thank you. I was also able to make it work using a function that will create my function. See the update on my post. – Michael Jan 21 '14 at 23:02
  • @YAmikep: As a side note, if you look at the [`inspect`](http://docs.python.org/3/library/inspect.html) docs for members of `function` and `code` objects, you can see how to test whether `p` is a local, closure, or global variable within the body. (You can also use `dis.dis` if you want to learn how to ready bytecode.) In more complex cases, it can be handy. – abarnert Jan 21 '14 at 23:07