Welcome, guest | Sign In | My Account | Store | Cart

This mixin makes it easy to provide a full dictionary interface to a class defining only a few mapping methods for getting, setting, deleting, and listing keys. Also, a function is provided to incorporate the mixin at runtime so that code for existing modules need not be modified.

Python, 133 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
class DictMixin:
    '''Mixin defining all dictionary methods for classes that already have
       a minimum dictionary interface including getitem, setitem, delitem,
       and keys '''

    # first level definitions should be implemented by the sub-class
    def __getitem__(self, key):
        raise NotImplementedError
    def __setitem__(self, key, value):
        raise NotImplementedError
    def __delitem__(self, key):
        raise NotImplementedError    
    def keys(self):
        raise NotImplementedError
    
    # second level definitions which assume only getitem and keys
    def has_key(self, key):
         return key in self.keys()
    def __iter__(self):
        for k in self.keys():
            yield k

    # third level uses second level instead of first
    def __contains__(self, key):
        return self.has_key(key)            
    def iteritems(self):
        for k in self:
            yield (k, self[k])

    # fourth level uses second and third levels instead of first
    def iterkeys(self):
        return self.__iter__()
    def itervalues(self):
        for _, v in self.iteritems():
            yield v
    def values(self):
        return list(self.itervalues())
    def items(self):
        return list(self.iteritems())
    def clear(self):
        for key in self.keys():
            del self[key]
    def setdefault(self, key, default):
        if key not in self:
            self[key] = default
            return default
        return self[key]
    def popitem(self):
        key = self.keys()[0]
        value = self[key]
        del self[key]
        return (key, value)
    def update(self, other):
        for key in other.keys():
            self[key] = other[key]
    def get(self, key, default):
        if key in self:
            return self[key]
        return default
    def __repr__(self):
        return repr(dict(self.items()))

def MakeFullDict(tgt):
    'Extends the dictionary interface for existing classes'
    tgt.__bases__ = tuple(list(tgt.__bases__) + [DictMixin])


### Example of extending shelve.py to have a full dictionary interface
import shelve
MakeFullDict(shelve.Shelf)

s = shelve.open('a.shl')
s['name'] = 'john'
s['world'] = 'earth'
print s.has_key('name')
print s.__contains__('name')
print 'name' in s
for k in s: print k
for k,v in s.iteritems(): print k,v
for k in s.iterkeys(): print k
for v in s.itervalues(): print v
print s.values()
print s.setdefault( 'addr', 1 )
print s.setdefault( 'addr', 2 )
del s['addr']
print s.popitem()
s.update( {'age':38} )
print s.keys()
print s.get('age',17)
print s.get('salary', 2000)
print s
print `s`

### Example of a binary tree expanding to a full dictionary iterface
class TreeDict(object, DictMixin):
    __slots__ = [left, right, key, value]
    def __init__(self, key, value):
        self.left, self.right, self.key, self.value = None, None, key, value   
    def __getitem__(self, key):
        if key == self.key: return value
        next = self.left
        if key > self.key: next=self.right
        if next is None:  raise KeyError, key
        return next[key]
    def __setitem__(self, key, value):
        if key == self.key:
            self.value = value
            return
        if key < self.key:
            if self.left is None:
                self.left = TreeDict(key,value)
            else:
                self.left[key]=value
        else:
            if self.right is None:
                self.right = TreeDict(key,value)
            else:
                self.right[key]=value
    def keys(self):
        ans = [self.key]
        if self.left is not None: ans.extend(self.left.keys())
        if self.right is not None:  ans.extend(self.right.keys())
        return ans
    def items(self):
        'DictMixin does this; but it can be done faster by the base class'
        ans = [(self.key, self.value)]
        if self.left is not None: ans.extend(self.left.items())
        if self.right is not None:  ans.extend(self.right.items())
        return ans    
    def iteritems(self):
        'DictMixin does this; but it can be done faster by the base class'
        for item in self.items():
            yield item

When defining your own classes that use __getitem__ and __setitem__ methods, it is cumbersome to encode every method in the full dictionary interface. Using this mixin as a base class will automatically extend your class to have all of the usual dictionary methods.

By using the mixin as a superclass, you are free to overwrite any of these methods with your own and not break the mixin. For instance, your class may already define the .get() method but not any of the iter functions.

The reason for implementing the full dictionary interface is to maximize substitutability. Given code that was designed to work with a dictionary, an object of your class (with the mixin) can be substituted for the dictionary.

Some modules like shelve implement a subset of the dictionary interface. If you want to substitute a persistent shelve for a dictionary in existing code, then its interface needs to be broadened to handle all of the dictionary methods.

One option would be to edit shelve.py and add the mixin directly. A better option is to add it at runtime using the MakeFullDict function as shown in the sample code. MakeFullDict tells the shelve class that it is a subclass of DictMixin. Afterwards, calls to methods not defined by shelve will be passed to the mixin code.

The technique is like reversing the Decorator design pattern. A decorator wraps around another object or class and adds functionality. Unfortunately, it can mask definitions in the object or class. The MakeFullDict technique inverts the pattern, wrapping the class around the mixin and assuring that no method is masked.

One commenter asked what is the difference between this mixin/framework and UserDict. The answer is that UserDict has an underlying dictionary and would not help to extend shelve which has a pickled database as the underlying structure. Also, UserDict would not help in the binary tree example for the same reason. In short, this mixin is for making a dictionary interface with objects that are not truly dictionaries.

1 comment

Andy Gimblett 22 years ago  # | flag

How does this differ from UserDict? Forgive me if I'm missing something, but how does this differ from/add more value than the standard library module "UserDict" (or, in python 2.2 or later, just inheriting directly from dictionary)?

http://www.python.org/doc/current/lib/module-UserDict.html
Created by Raymond Hettinger on Thu, 14 Mar 2002 (PSF)
Python recipes (4591)
Raymond Hettinger's recipes (97)
HongxuChen's Fav (39)

Required Modules

Other Information and Tasks