views:

95

answers:

2

I'm trying to use Python's @property decorator on a dict in a class. The idea is that I want a certain value (call it 'message') to be cleared after it is accessed. But I also want another value (call it 'last_message') to contain the last set message, and keep it until another message is set. In my mind, this code would work:

>>> class A(object):
...     def __init__(self):
...             self._b = {"message": "", 
...                        "last_message": ""}
...     @property
...     def b(self):
...             b = self._b
...             self._b["message"] = ""
...             return b
...     @b.setter
...     def b(self, value):
...             self._b = value
...             self._b["last_message"] = value["message"]
...
>>>

However, it doesn't seem to:

>>> a = A()
>>> a.b["message"] = "hello"
>>> a.b["message"]
''
>>> a.b["last_message"]
''
>>>

I'm not sure what I have done wrong? It seems to me like @property doesn't work like I would expect it to on dicts, but maybe I'm doing something else fundamentally wrong?

Also, I know that I could just use individual values in the class. But this is implemented as a session in a web application and I need it to be a dict. I could either make this work, or make the whole session object to pretend it's a dict, or use individual variables and hack it into workingness throughout the rest of the code base. I would much rather just get this to work.

Update

Thanks to ~unutbu's help with subclassing dict, I came up with the following solution and dropped properties altogether:

>>> class MessageDict(dict):
...     def __setitem__(self, key, value):
...             if key == "message":
...                     dict.__setitem__(self, "last_message", value)
...             dict.__setitem__(self, key, value)
...     def __getitem__(self, key):
...             value = dict.__getitem__(self, key)
...             if key == "message":
...                     dict.__setitem__(self, key, "")
...             return value
...
>>> a = MessageDict({"message": "", "last_message": ""})
>>> a
{'last_message': '', 'message': ''}
>>> a['message'] = "hello"
>>> a['message']
'hello'
>>> a['message']
''
>>> a['last_message']
'hello'
>>>
+1  A: 

I may be missing what you are trying to do here, but does this solve your problem?

class A(object):
    def __init__(self):
        self._b = {'message':'',
                   'last_message': ''}

    @property
    def b(self):
        b = self._b.copy()
        self._b['message'] = ''
        return b

    @b.setter
    def b(self, value):
        self._b['message'] = value
        self._b['last_message'] = value


if __name__ == "__main__":
    a = A()
    a.b = "hello"
    print a.b
    print a.b
    print a.b["last_message"]

$ python dictPropTest.py
{'last_message': 'hello', 'message': 'hello'}
{'last_message': 'hello', 'message': ''}
hello
sberry2A
close, I actually just discovered that assigning to a key of a setter doesn't really work. I was thinking that `a.b['message'] = "hello"` would pass a dict containing a `message` key to the setter, but that's not the case. But what I really want is to be able to assign to a key of a setter
Carson Myers
+4  A: 
class MyDict(dict):
    def __setitem__(self,key,value):
        if key=='message':
            dict.__setitem__(self,'message','')
            dict.__setitem__(self,'last_message',value) 
        else:
            dict.__setitem__(self,key,value)

class A(object):
    def __init__(self):
            self._b = MyDict({"message": "", 
                       "last_message": ""})
    @property
    def b(self):
        return self._b

a=A()
a.b['message']='hello'
print(a.b['message'])
# ''
print(a.b['last_message'])
# hello

As I think you've discovered, the reason why your setter wasn't working is because

a.b['message']='hello'

first accesses a.b, which calls the b property's getter, not its setter. The getter returns the dict self._b. Then self._b['message']='hello' causes the dict's __setitem__ is called .

So to fix the problem, you need a special dict (like MyDict).

unutbu
Thank you, I made a special dict and it's now working. I dropped properties altogether and just moved all of the functionality into the dict, though (with `__getitem__`). I'll add my own code to the question as well.
Carson Myers