最近趁着端午假期,终于把 《流畅的 Python》看完了,收获很大,书中某一章节介绍了 addict 库,可以将 Json 转换为 Python Object 。 今天看了看具体的实现方式,比 Stack Overflow 的回答完整,补发一篇博客学习下。

源码代码很少,补充一些关键变量用于了解整体实现流程。

import copy
from pprint import pprint


class Dict(dict):

    def __init__(__self, *args, **kwargs):
        print 'init kwargs is ', kwargs
        object.__setattr__(__self, '__parent', kwargs.pop('__parent', None))
        object.__setattr__(__self, '__key', kwargs.pop('__key', None))
        for arg in args:
            if not arg:
                continue
            elif isinstance(arg, dict):
                for key, val in arg.items():
                    __self[key] = __self._hook(val)
            elif isinstance(arg, tuple) and (not isinstance(arg[0], tuple)):
                __self[arg[0]] = __self._hook(arg[1])
            else:
                for key, val in iter(arg):
                    __self[key] = __self._hook(val)

        for key, val in kwargs.items():
            __self[key] = __self._hook(val)

    def __setattr__(self, name, value):
        if hasattr(Dict, name):
            raise AttributeError("'Dict' object attribute "
                                 "'{0}' is read-only".format(name))
        else:
            self[name] = value

    def __setitem__(self, name, value):
        print 'setitem name is %s, value is %s ' % (name, value)
        super(Dict, self).__setitem__(name, value)
        try:
            p = object.__getattribute__(self, '__parent')
            key = object.__getattribute__(self, '__key')
        except AttributeError:
            p = None
            key = None
        if p is not None:
            print 'parent is %s' % p
            print 'key is %s' % key
            p[key] = self
            object.__delattr__(self, '__parent')
            object.__delattr__(self, '__key')

    def __add__(self, other):
        if not self.keys():
            return other
        else:
            self_type = type(self).__name__
            other_type = type(other).__name__
            msg = "unsupported operand type(s) for +: '{}' and '{}'"
            raise TypeError(msg.format(self_type, other_type))

    @classmethod
    def _hook(cls, item):
        if isinstance(item, dict):
            return cls(item)
        elif isinstance(item, (list, tuple)):
            return type(item)(cls._hook(elem) for elem in item)
        return item

    def __getattr__(self, item):
        return self.__getitem__(item)

    def __getitem__(self, name):
        print 'getitem name is ', name
        if name not in self:
            print "%s not in self" % name
            return Dict(__parent=self, __key=name)
        return super(Dict, self).__getitem__(name)

    def __delattr__(self, name):
        del self[name]

    def to_dict(self):
        base = {}
        for key, value in self.items():
            if isinstance(value, type(self)):
                base[key] = value.to_dict()
            elif isinstance(value, (list, tuple)):
                base[key] = type(value)(
                    item.to_dict() if isinstance(item, type(self)) else
                    item for item in value)
            else:
                base[key] = value
        return base

    def copy(self):
        return copy.copy(self)

    def deepcopy(self):
        return copy.deepcopy(self)

    def __deepcopy__(self, memo):
        other = self.__class__()
        memo[id(self)] = other
        for key, value in self.items():
            other[copy.deepcopy(key, memo)] = copy.deepcopy(value, memo)
        return other

    def update(self, *args, **kwargs):
        other = {}
        if args:
            if len(args) > 1:
                raise TypeError()
            other.update(args[0])
        other.update(kwargs)
        for k, v in other.items():
            if ((k not in self) or
                (not isinstance(self[k], dict)) or
                    (not isinstance(v, dict))):
                self[k] = v
            else:
                self[k].update(v)

    def __getnewargs__(self):
        return tuple(self.items())

    def __getstate__(self):
        return self

    def __setstate__(self, state):
        self.update(state)

    def setdefault(self, key, default=None):
        if key in self:
            return self[key]
        else:
            self[key] = default
            return default


json_data = {
    "a": "a"
}

test1 = Dict(json_data)
test1.b.c = "c"
test1.b.d.e = "e"
pprint(test1.to_dict())

 $ python a.py
init kwargs is  {}
setitem name is a, value is a 
getitem name is  b
b not in self
init kwargs is  {'__key': 'b', '__parent': {'a': 'a'}}
setitem name is c, value is c 
parent is {'a': 'a'}
key is b
setitem name is b, value is {'c': 'c'} 
getitem name is  b
getitem name is  d
d not in self
init kwargs is  {'__key': 'd', '__parent': {'c': 'c'}}
setitem name is e, value is e 
parent is {'c': 'c'}
key is d
setitem name is d, value is {'e': 'e'} 
{'a': 'a', 'b': {'c': 'c', 'd': {'e': 'e'}}}