Cache-Commit Dictionaries (was: Re: survey: is shelve broken?)

holger krekel pyth at devel.trillke.net
Thu May 9 10:05:56 EDT 2002


Hello Alex, hello everybody,

After some seriously thinking about your
suggestion/patch for fixing the behaviour
of 'shelve' i think i have found a better solution
which

- avoids changing the shelve implementation
  at all and so avoids the previously discussed 
  incompatibilities.

- introduce a more general purpose "cache-commit"
  Dictionary which is useful for many cases.
  It's not much larger than the special-cased
  shelve-patch on sf.

A cache-commit dictionary ('ccdict' for short) is 
a very *simple* transactional Wrapper-Dictionary with
the following (to be further discuessed) properties:

- It wraps a dictionary-like object which
  in turn only needs to provide 
  __getitem__, __setitem__, __delitem__ 
  
- on first access of an element 
  ccdict makes a lookup on the underlying
  dict and caches the item. 

- the next accesses work with the cached thing. 
  dict-semantics as expected (e.g. your case) 
  are provided. 

- commit() transfers the items in the
  cache to the underlying dict and clears
  the cache.

- deleting an item is deferred and actually happens 
  on commit() time. deleting an item and later on
  assigning to it works as expected. 

- deleting an ccdict-instance does *not* commit any changes.
  You have to explicitely call commit().

- clear() only cleares the cache and not the underlying
  dict (my intuition, yours also?)

- setdefault/get work as expected.

- (not implemented) provide iterator semantics?

The name 'cache-commit' refers to the two
prime characteristics:

- caching accesses and 

- committing in one go all changes.

The general ccdict does not call any sync/close/open 
functions. You have to feed it a dictish/shelvish object 
yourself!

But i have included a 15-line ccshelve which
uses ccdict and provides this functionality:

>>> c = ccshelve('/tmp/testshelve', factory=shelve.open)
>>> c['hello']='world'
>>> c['liste']=[1,2,3]
>>> c['liste'].append(42)
>>> c.close()
>>> c.reopen()
>>> print c['liste']
[1, 2, 3, 42]

Just execute the attached proof-of-concept-code 
and you'll (hopefully) see the testexample running and 
can directly start fiddling with it yourself. Hope you like it and
stop thinking that i want to infest the world
with globals, implicitisms and 'exec'-code :-)

regards,

    holger
-------------- next part --------------
#!/usr/bin/env python

# requires python 2.2
class ccdict(dict):
    """ cache-commit dictionary
        must be initialized with a dictish-object.
        self.STATEDICT is the underlying dict
        (in capital letters to visually emphasize)
    """
    def __init__(self, STATEDICT):
        dict.__init__(self)
        self.STATEDICT = STATEDICT
        self.deleted={}

    def get(self, key, default=None):
        if dict.has_key(self,key):
            v=dict.__getitem(self,key)
        else:
            if self.deleted.get(key):
                v=self[key]=default
                del deleted[key]
            else:
                v=self[key]=self.STATEDICT.get(key,default)
        return v

    def __getitem__(self, key):
        if dict.has_key(self,key):
            v = dict.__getitem__(self,key)
        else:
            if self.deleted.has_key(key):
                raise KeyError,"'%s' has been deleted" % key
            v=self[key]=self.STATEDICT[key]
        return v

    def has_key(self, key):
        if dict.has_key(self,key):
            return 1
        if self.deleted.has_key(key):
            return 0
        if self.STATEDICT.has_key(key):
            return 1
        return 0

    def __delitem__(self, key):
        if self.STATEDICT.has_key(key):
            if self.deleted.has_key(key):
                raise KeyError,"'%s' has been deleted" % key
            self.deleted[key]=1
            if not self.has_key(key):
                return
        dict.__delitem__(self, key)

    def clear(self):
        self.deleted.clear()
        dict.clear(self)
    
    def setdefault(self, key, default):
        if self.has_key(key):
            v = dict.__getitem__(self,key)
        else:
            if self.deleted.has_key(key):
                v=self[key]=default
                del self.deleted[key]
            else:
                if self.STATEDICT.has_key(key):
                    v=self[key]=self.STATEDICT[key]
                else:
                    v=self[key]=default
        return v

    def commit(self):
        for key,obj in self.items():
            self.STATEDICT[key]=obj
            self.deleted[key]=0
        for key,value in self.deleted.items():
            if value:
                del self.STATEDICT[key]
        dict.clear(self)
        self.deleted={}

import shelve
class ccshelve(ccdict):
    """ quick example giving cache-commit 
        access to a shelve
    """
    def __init__(self, name, factory=shelve.open):
        self.name=name
        self.factory=factory
        self.shelf=factory(name)
        ccdict.__init__(self, self.shelf)

    def sync(self):
        self.commit()
        self.shelf.sync()

    def __del__(self):
        self.shelf.close()

    def close(self):
        self.sync()
        self.shelf.close()  
        self.shelf=None

    def reopen(self):
        if self.shelf:
            self.shelf.close() 
        self.shelf = self.factory(self.name)
        ccdict.__init__(self,self.shelf)

    def clear(self):
        raise NotImplementError,"clear() semantics not yet defined"

if __name__=='__main__':
    testexample="""
# yes i know about doctest :-)
from __main__ import ccshelve

c = ccshelve('/tmp/testshelve')
c['hello']='world'
c['liste']=[1,2,3]
c['liste'].append(42)
c.commit()
c.reopen()

del c['hello']
try:
    c['hello']
except KeyError:
    pass
else:
    print "never executed!"

print c.setdefault('hello',['python'])
print c.get('nonexisting','nonexisting key, ok')
c['hello'].append('rules')
print c['hello']

c.close()
c.reopen()
print c['hello']
"""
    import code
    ic = code.InteractiveConsole()
    for line in testexample.split('\n'):
        print ">>>",line
        ic.push(line)
    ic.interact('use c.reopen() to continue fiddling around')

        
        
    


More information about the Python-list mailing list