Reputation: 22827
I have a class with a property like this:
class Foo(object):
def __init__(self):
self._bar = 'bar'
@property
def bar(self):
return self._bar
@bar.setter
def bar(self, value):
self._bar = value
I instantiated that class, used it in a dictionary and assigned a new value to the property like this:
f = Foo()
d = {'x': f.bar}
d['x'] = 'baz'
print d['x'] # prints baz, as expected
print f.bar # prints bar, not as expected
Why is the setter of the property not called when I use it in this way?
Edit: This visualization made things already a lot clearer: if I understand it correctly, what is stored in the dictionary is not an object Foo
with a property bar
, but the return value of the getter. Right?
Upvotes: 1
Views: 1334
Reputation: 978
Dictionaries overwrite their values when a same key is used twice.
>>> test_dict = {'a': 1, 'b': 2, 'c': 3}
>>> test_dict['a']
1
>>> test_dict['a'] = 5
>>> test_dict
{'a': 5, 'c': 3, 'b': 2}
In your code
f = Foo()
d = {'x': f.bar} # map 'x' to f.bar - and your getter property is invoked
d['x'] = 'baz' # remap 'x' to baz
print d['x'] # prints baz, as expected
print f.bar # prints bar, again getter is invoked here
Edit:
if I understand it correctly, what is stored in the dictionary is not an object Foo with a property bar, but the return value of the getter. Right?
Yes, getters can be used for performing some actions. For example say you need bar with first letter caps, you could modify your data and return that in the getter.
@property
def bar(self):
return self._bar.capitalize()
Similarly setters can be used for sanity testing. Say you don't need string called 'stupid' to be stored in your data - you could do something like
@bar.setter
def bar(self, value):
if value == 'stupid':
raise ValueError("Stupid not allowed!")
self._bar = value
Basically, what you need to remember is, when a value is set, the setter
is invoked and when the value is retrieved the getter
is invoked. In your code though, the setter
is never invoked.
Upvotes: 1
Reputation: 251373
When you do d = {'x': f.bar}
, you are setting the dict's 'x'
value to the current value of f.bar
--- the value at the time you create the dict. It does not create a "link" or reference that reads f.bar
every time you read the dict value (or sets it every time you set the dict value). There is no link between the object property value and the dict value. (This is essentially what you said in your edit: what is stored in the dict is the value of f.bar
; the value doesn't "know" that it was obtained by readng f.bar
, so it is decoupled from any future changes to f.bar
.)
There's no real way to create such a link without changing the way you use the object/dictionary. That is, there's no way to create a plain dict so that d['x'] = 2
actually does f.bar = 2
. You could create a custom proxy object class that does this, and use that instead of a dict. Or you could make the dict hold the attribute name, and use getattr
/setattr
to read/set the value:
d = {'x': 'bar'}
getattr(f, d['x']) # gives the value of f.bar
setattr(f, d['x'], 2) # sets f.bar = 2
It's not clear from your question what your larger goal is with this code, so it's hard to say what the appropriate solution is.
Upvotes: 4