I want to be able to create python objects with instance attributes defined dynamically from a dictionary. I am aware of at least two ways of doing this, simply calling setattr(self, k, v)
or alternatively calling setattr(FooClass, k, property(lambda x: v))
. The first option works completely fine, but I can't get the second to do what I want. Consider the following code
class A(object):
def __init__(self, d):
for k in d:
setattr(A, k, property(lambda x: ('Value(%s)' % d[k], 'Key(%s)' % k, d)))
>>> a = A({'foo':'1','bar':'2'})
>>> a.foo
('Value(2)', 'Key(bar)', {'bar': '2', 'foo': '1'})
Why does a.foo
not return the value of foo
in the dictionary? The property
objects and their getters are distinct.
>>> A.foo, A.bar
<property at 0x1106c1d60>, <property at 0x1106c1d08>
>>> A.foo.__get__
<method-wrapper '__get__' of property object at 0x1106c1d60>
>>> A.bar.__get__
<method-wrapper '__get__' of property object at 0x1106c1d08>
There are quite a few related questions on SO, the best one being this (How to add property to a class dynamically?) and Eevee
's answer. That doesn't quite explain the behaviour though.
These deal with attributes on a class instance, not Property
objects on the Class
- Python extremely dynamic class properties
- How to dynamically assign values to class properties in Python?
UPDATE
Thanks to Martijn Pieters for his help (answer). The answer lies in the scope of the lambda function.
>>> A.bar.fget.__closure__[0], A.foo.fget.__closure__[0]
<cell at 0x1106c63d0: dict object at 0x7f90f699fe80>, <cell at 0x1106c63d0: dict object at 0x7f90f699fe80>
Working version:
class A(object):
def __init__(self, d):
for k, v in d.items():
setattr(A, k, property(lambda x, v=v, k=k: ('Value(%s)' % v, 'Key(%s)' % k, d)))
Although this actually modifies every instance of the class to have the same attribute values.
Lesson:
be careful with
lambda
functions inside loops