如何定义一个超级强大的类式dict对象?

时间:2013-10-07 10:05:13

标签: python class

假设满足我需要的对象被称为classdic,那么类classdic的实例的功能是:

  1. 查询,更新,添加和删除数据都可以用类风格和dict风格方式实现(称为“两种方式”)。

  2. 当一个属性或一个键不存在时,classdic的实例可以自动构建它并使它等于实例和dict上的默认值,因此我们可以两种方式查询它(注意:不添加,只是查询)。

  3. 那么,我该如何实现这个类?

    下面的示例显示了此类的实例如何工作:

    dic={'one':1,
    'two':{'four':4,'five':{'six':6,'seven':7}},
    'three':3}
    
    cdic=classdic(dic,default=100)
    
    -------------------query in two ways-------------------------------------------
    >>> cdic.one
    1
    >>> cdic.two
    {'four':4,'five':{'six':6,'seven':7}}
    >>> cdic.two.five.six
    6
    >>> cdic['two']['five']['six']
    6
    -------------------update in two ways-------------------------------------------
    >>> cdic['two']['five']['six']=7
    >>> cdic.two.five.six
    7
    >>> cdic.two.five.six=8
    >>> cdic['two']['five']['six']
    8
    -------------------add in two ways-------------------------------------------
    >>> cdic['two']['five']['eight']=8
    >>> cdic.two.five.eight
    8
    >>> cdic.two.five.nine=9
    >>> cdic['two']['five']['nine']
    9
    -------------------query default in two ways-------------------------------------------
    >>> print cdic['ten']
    100
    >>> cdic.ten
    100
    >>> print cdic.eleven
    100
    >>> cdic['eleven']
    100
    -------------------the final state of cdic-------------------------------------------
    >>> cdic
    {'eleven': 100, 'three': 3, 'two': {'four': 4, 'five': {'nine': 9, 'seven': 7, 'six': 8, 'eight': 8}}, 'ten': 100, 'one': 1}
    

4 个答案:

答案 0 :(得分:4)

子类collections.defaultdict()

from collections import defaultdict, Mapping

class default_attribute_dict(defaultdict):
    def __init__(self, *args, **kwargs):
        super(default_attribute_dict, self).__init__(*args, **kwargs)
        self.__dict__ = self

    def __getattr__(self, name):
        # trigger default
        return self[name]

    @classmethod
    def from_dictionaries(cls, d, default=lambda: None):
        cdic = cls(default)
        for key, value in d.iteritems():
            if isinstance(value, Mapping):
               value = cls.from_dictionaries(value, default=default)
            cdic[key] = value
        return cdic

这将自动创建自己的嵌套实例;你需要循环输入字典并自己创建嵌套对象。

但它确实提供了属性访问和默认值:

>>> cdic = default_attribute_dict(lambda: 100)
>>> cdic.hundred
100
>>> cdic['ten'] = 10
>>> cdic.ten
10
>>> cdic['ten']
10

要从现有字典构建树,请使用from_dictionaries()类方法:

>>> cdic = default_attribute_dict.from_dictionaries(dic, default=lambda: 100)
>>> cdic
defaultdict(<function <lambda> at 0x109998848>, {'one': 1, 'three': 3, 'two': defaultdict(<function <lambda> at 0x109998848>, {'four': 4, 'five': defaultdict(<function <lambda> at 0x109998848>, {'seven': 7, 'six': 6})})})
>>> cdic.two.four
4

请注意,字典上的键可以掩盖方法;插入与字典方法匹配的键时请记住这一点:

>>> cdic = default_attribute_dict.from_dictionaries(dic, default=lambda: 100)
>>> cdic.keys
<built-in method keys of default_attribute_dict object at 0x7fdd0bcc9ac0>
>>> cdic['keys']
100
>>> cdic.keys
100
>>> cdic.keys()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable

答案 1 :(得分:0)

如果你想能够做到

cdic.two = 2 

您还应该覆盖 __ setattr __

def __setattr__(self, name, val):
    self[name] = val

答案 2 :(得分:0)

以下是Martijn Pieters'answer的变体,它是此answer的变体(其本身是this的变体,基于blog entry by詹姆斯罗伯特)。但是,与Martijn不同,它将 自动创建自己的嵌套实例。还添加了自定义__repr__()

from collections import defaultdict, Mapping

class classdict(defaultdict):
    def __init__(self, *args, **kwargs):
        super(classdict, self).__init__(*args, **kwargs)
        self.__dict__ = self

    def __getattr__(self, name):
        return self[name]  # trigger default

    def __missing__(self, key):
        default = (None if self.default_factory is None else
                   self.default_factory())
        self[key] = classdict.from_dict({key: default},
                                        default=self.default_factory)
        return self[key]

    def __repr__(self):
        return '{}({}, default={})'.format(self.__class__.__name__,
                                           dict(self.__dict__),  # no recursion
                                           self.default_factory)
    @classmethod
    def from_dict(cls, d, default=lambda: None):
        cdic = cls(default)
        for key, value in d.iteritems():
            cdic[key] = (value if not isinstance(value, Mapping)
                         else cls.from_dict(value, default=default))
        return cdic

if __name__ == '__main__':

    dic={'one': 1,
         'two': {'four': 4,
                 'five': {'six': 6,
                          'seven': 7}
                },
         'three': 3
        }

    cdic = classdict.from_dict(dic, default=lambda: 100)

    print 'cdic.one:', cdic.one
    print 'cdic.two:', cdic.two
    print 'cdic.two.five.six:', cdic.two.five.six
    print "cdic['two']['five']['six']:", cdic['two']['five']['six']
    print "cdic['two']['five']['six'] = 7"
    cdic['two']['five']['six'] = 7
    print 'cdic.two.five.six:', cdic.two.five.six
    print 'cdic.two.five.six = 8'
    cdic.two.five.six = 8
    print "cdic['two']['five']['six']:", cdic['two']['five']['six']
    print "cdic['two']['five']['eight'] = 8"
    cdic['two']['five']['eight'] = 8
    print 'cdic.two.five.eight:', cdic.two.five.eight
    print 'cdic.two.five.nine = 9'
    cdic.two.five.nine = 9
    print "cdic['two']['five']['nine']:", cdic['two']['five']['nine']
    print "cdic['ten']:", cdic['ten']
    print 'cdic.ten:', cdic.ten
    print 'cdic.eleven:', cdic.eleven
    print "cdic['eleven']:", cdic['eleven']
    print "final cdic:\n    ", cdic

答案 3 :(得分:0)

在研究了所有答案后,我自己编写了解决方案。

它很好地满足了我的需求,并且不需要“import xxx”,也很适合打印。

(我认为我比昨天计划分享的this one更好)

我的解决方案:

class DictObj(dict):
    default=None
    def __init__(self, dic, default):
        DictObj.default = default
        for key,value in dic.items():
            if isinstance(value,dict):
                self.__setattr__(key, DictObj(value,DictObj.default))
            else:
                self.__setattr__(key, value)  
    def __getitem__(self, key):
        return self.__getattr__(key )

    def __setitem__(self, key, value):
        self.__setattr__(key,value)

    def __getattr__( self ,key ):
        if key not in self:
            self.__setattr__(key,DictObj.default)
        return self.__dict__[key] 

    def __setattr__( self ,key ,value ):
        self.__dict__[key]=value
        dict.__setitem__(self, key, value)

    def printself(self):
        print self     

dic={'one':1,
     'two':{
         'four':4,
         'five':{
             'six':6,
             'seven':7,}},
     'three':3}

cdic=DictObj(dic,100)

print '-------------------the start state of cdic-------------------------------------------'
print cdic

print '-------------------query in two ways-------------------------------------------'
print 'cdic.two.five-->',cdic.two.five
print "cdic['two']['five']-->",cdic['two']['five']
print 'cdic.two.five.six-->',cdic.two.five.six
print "cdic['two']['five']['six']-->",cdic['two']['five']['six']

print '-------------------update in two ways-------------------------------------------'
cdic['two']['five']['six']=7
print "cdic['two']['five']['six']=7"
print "cdic.two.five.six-->",cdic.two.five.six 

cdic.two.five.six=6
print "cdic.two.five.six=6"
print "cdic['two']['five']['six']-->",cdic['two']['five']['six']

print '-------------------add in two ways-------------------------------------------'

cdic['two']['five']['eight']=8
print "cdic['two']['five']['eight']=8"
print "cdic.two.five.eight-->",cdic.two.five.eight

cdic.two.five.nine=9
print "cdic.two.five.nine=9"
print "cdic['two']['five']['nine']-->",cdic['two']['five']['nine']

print '-------------------query default in two ways-------------------------------------------'
print "cdic['ten']-->",cdic['ten']
print "cdic.eleven-->",cdic.eleven
print "cdic.two.five.twelve-->",cdic.two.five.twelve
print '-------------------the final state of cdic-------------------------------------------'

print cdic,'\n'
cdic.printself()

结果:

-------------------the start state of cdic-------------------------------------------
{'one': 1, 'three': 3, 'two': {'four': 4, 'five': {'seven': 7, 'six': 6}}}
-------------------query in two ways-------------------------------------------
cdic.two.five--> {'seven': 7, 'six': 6}
cdic['two']['five']--> {'seven': 7, 'six': 6}
cdic.two.five.six--> 6
cdic['two']['five']['six']--> 6
-------------------update in two ways-------------------------------------------
cdic['two']['five']['six']=7
cdic.two.five.six--> 7
cdic.two.five.six=6
cdic['two']['five']['six']--> 6
-------------------add in two ways-------------------------------------------
cdic['two']['five']['eight']=8
cdic.two.five.eight--> 8
cdic.two.five.nine=9
cdic['two']['five']['nine']--> 9
-------------------query default in two ways-------------------------------------------
cdic['ten']--> 100
cdic.eleven--> 100
cdic.two.five.twelve--> 100
-------------------the final state of cdic-------------------------------------------
{'eleven': 100, 'one': 1, 'three': 3, 'ten': 100, 'two': {'four': 4, 'five': {'nine': 9, 'seven': 7, 'six': 6, 'eight': 8, 'twelve': 100}}}

{'eleven': 100, 'one': 1, 'three': 3, 'ten': 100, 'two': {'four': 4, 'five': {'nine': 9, 'seven': 7, 'six': 6, 'eight': 8, 'twelve': 100}}}