Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using @property decorator on dicts

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.

like image 752
Carson Myers Avatar asked Jun 29 '10 03:06

Carson Myers


2 Answers

class MyDict(dict):
    def __setitem__(self, key, value):
        if key == 'message':
            super().__setitem__('message', '')
            super().__setitem__('last_message', value) 
        else:
            super().__setitem__(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).

like image 151
unutbu Avatar answered Oct 14 '22 05:10

unutbu


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
like image 20
sberry Avatar answered Oct 14 '22 06:10

sberry