BioGeek
BioGeek

Reputation: 22827

Setter of a property used in a dictionary

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

Answers (2)

ersran9
ersran9

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

BrenBarn
BrenBarn

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

Related Questions