How to change the behavior of a python dictionary&

2019-04-10 06:26发布

问题:

In Python, everything has a class. Therefore dict also has a class.

So, in theory, I should be able to change the implementation of the keyvalue assignment behavior.


Example:

d = dict()
d['first'] = 3    # Internally d['first'] is stored as 6 [i.e. value*2 if value is INT]

print d['first']  # should print 6

d['second'] = 4 

print d['second'] # should print 8


I noticed that most objects have attributes listed in OBJECT.__dict__ or vars(OBJECT). But this isn’t the case for dict or list.

How can I get the desired behavior by overriding dict.__setattr__() method?

回答1:

It is __setitem__ that have to be overriden in this case - and it is as simples as:

class MyDict(dict):
    def __setitem__(self, key, value):
         dict.__setitem__(self, key, 2 * value)

Example:

>>> m  = MyDict()
>>> m[0] = 5
>>> m
{0: 10}

__setattr__ controls how object attributes themselves (not key/value pairs) are attributed.



回答2:

Be careful when subclassing dict. If you just override __setitem__, then other dict methods, such as update, will not call your __setitem__.

class MyDict(dict):
    def __setitem__(self, key, value):
         dict.__setitem__(self, key, 2 * value)

d = MyDict()
d['first'] = 3
print(d['first'])
# 6

d.update({'first':4})
print(d['first'])
# 4                       # <--- __setitem__ was not called.

In order to create a dict-like object, you either need to subclass dict and override all the methods (see OrderedDict for an example of this approach), or subclass collections.MutableMapping and override a small subset those methods (from which all the other methods are derived).

import collections

class MyDict2(collections.MutableMapping,dict):
    def __getitem__(self, key):
        return dict.__getitem__(self, key)
    def __setitem__(self, key, value):
         dict.__setitem__(self, key, 2 * value)
    def __delitem__(self, key):
        dict.__delitem__(self, key)
    def __iter__(self):
        return dict.__iter__(self)
    def __len__(self):
        return dict.__len__(self)
    def __contains__(self, x):
        return dict.__contains__(self, x)

d = MyDict2()
d['first'] = 3
print(d['first'])
# 6
d.update({'first':4})
print(d['first'])
# 8


回答3:

You can’t. The dict class is written in C a Python builtin, which means you can’t really shouldn’t do any kind of monkey-patching on it.

You can, however, inherit from it and override whatever methods you want in your subclass.



回答4:

If you run the following code in the console you will see that the dict class/object is read-only:

{}.__setitem__ = None
AttributeError: 'dict' object attribute '__setitem__' is read-only

You need to what @jsbueno posted, which is create a subclass of the dict class, override the __setitem__ method, multiply the value by two and then call the original dict __setitem__ method.