1

Assume that the object meeting my need is called classdic, then the functions of an instance of class classdic are:

  1. Query, update, add, and delete data can be all realized in class style way and dict style way (called "two ways").

  2. When an attribute or a key not existing, the instance of classdic can automatically build it and make it equal the default value on both instance and dict, thus we can query it both ways (note: not add, just query).

So, how can I implement this class?

The example below shows how an instance of this class would work:

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}
martineau
  • 119,623
  • 25
  • 170
  • 301
tcpiper
  • 2,456
  • 2
  • 28
  • 41
  • In the final state of cdic, how did items `'ten': 10` and `'eleven': 11` come into being? – martineau Oct 07 '13 at 13:02
  • @martineau thanks.i've corrected it.this example is actually a simulation .I guess when I pasted the data I forgot something:-) – tcpiper Oct 07 '13 at 13:34
  • @martineau Oh,I decide to share another wonderful answer to my question from another website.I press the Button"Answer Your Question" then a message box tells me "Are you sure you want to answer your own question".it seems that this action is not encouraged.am I right? or which way is the best to share other answers? – tcpiper Oct 07 '13 at 13:43
  • Answering your own question is fine. – martineau Oct 07 '13 at 15:48

4 Answers4

4

Subclass 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

This will not automatically create nested instances of itself; you'll need to loop over the input dictionary and create nested objects yourself.

But it does offer attribute access and default values:

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

To build your tree from an existing dictionary, use the from_dictionaries() class method:

>>> 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

Note that keys on the dictionary can mask methods; keep that in mind when inserting keys that match dictionary methods:

>>> 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
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 1
    It is probably also worth mentioning that the behaviour of this may be surprising if actually used. e.g. `>>> cdic.opaque` gives `100`, but `cdic.clear` gives `` – Duncan Oct 07 '13 at 10:15
  • @Duncan: It is worse than that, actually. See the updated example. You can accidentally *mask* method names. – Martijn Pieters Oct 07 '13 at 10:20
  • Should (would it be most Pythonic) `from_dictionaries` be a `@classmethod` on `default_attribute_dict`, since it's an alternate constructor? – thebjorn Oct 07 '13 at 10:24
  • @thebjorn: Indeed, I was planning to do that anyway. :-) – Martijn Pieters Oct 07 '13 at 10:29
  • thank you!another question.Before override those special methods(such as __init__,__getattr__),where can I get the defination code for them?you know,because of this,I just don't know whether should I wrote a "return" in the method to be overrided or not= =how poor I am. – tcpiper Oct 07 '13 at 12:17
  • 1
    You mean documentation? See [`object.__init__()`](http://docs.python.org/2/reference/datamodel.html#object.__init__) (no return value required) and [`object.__getattr__()`](http://docs.python.org/2/reference/datamodel.html#object.__getattr__) (return value is attribute value for the given attribute name). – Martijn Pieters Oct 07 '13 at 12:19
  • I mean code.for example,when you define a dict object such as "dic={1:2,3:4}",dic has have methods like:__getitem__ or __setitem__ already.there must be some code to define how these methods work.that's just what I want to know.Something like:if you don't override the __setattr__,where can I see its defination? – tcpiper Oct 07 '13 at 12:50
  • @Pythoner: those methods are defined by the C code for `dict`; how good is your C fu? :-) `dict.__getattr__` uses a generic `PyObject_GenericGetAttr` function, the `__setattr__` slot is left empty. – Martijn Pieters Oct 07 '13 at 12:50
  • @Pythoner: as the `__setattr__` slot is left empty, the fallback is to use the `PyObject_GenericSetAttr` function there. See the [object protocol documentation](http://docs.python.org/2/c-api/object.html) and [`object.c`](http://hg.python.org/cpython/file/9524e1468913/Objects/object.c) for the implementation. – Martijn Pieters Oct 07 '13 at 12:58
  • Cry....if my python level is Qomolangma,my C level is East African Great Rift Valley.ok,no more question.thank you again. – tcpiper Oct 07 '13 at 13:00
  • @Pythoner: but basically, the generic `__getattr__` for `dict` only returns pre-defined attributes (so defined in the C code structures), `__setattr__` will either tell you the attribute doesn't exist or is read-only. By creating a custom subclass we open up the type to arbitrary attributes. Your best bet is the datamodel documentation here, really. – Martijn Pieters Oct 07 '13 at 13:02
0

If you want to be able to do

cdic.two = 2 

You should also override __setattr__

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

Here's a variation of Martijn Pieters' answer which is a variation of this answer (which itself is a variation of this, which is based on a blog entry by James Robert). However, unlike the Martijn's it will automatically create nested instances of itself. A custom__repr__()has also been added.

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
Community
  • 1
  • 1
martineau
  • 119,623
  • 25
  • 170
  • 301
0

After studying all the answers,I wrote the solution by myself.

It meets my needs well and no "import xxx" is required,also kind to print.

(I think mine is better than this one I have planned to share yesterday)

my solution:

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 result:

-------------------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}}}
tcpiper
  • 2,456
  • 2
  • 28
  • 41