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.
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.
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)?