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.