1# 2# This is a copy on write dictionary and set which abuses classes to try and be nice and fast. 3# 4# Copyright (C) 2006 Tim Ansell 5# 6#Please Note: 7# Be careful when using mutable types (ie Dict and Lists) - operations involving these are SLOW. 8# Assign a file to __warn__ to get warnings about slow operations. 9# 10 11 12import copy 13import types 14ImmutableTypes = ( 15 bool, 16 complex, 17 float, 18 int, 19 tuple, 20 frozenset, 21 str 22) 23 24MUTABLE = "__mutable__" 25 26class COWMeta(type): 27 pass 28 29class COWDictMeta(COWMeta): 30 __warn__ = False 31 __hasmutable__ = False 32 __marker__ = tuple() 33 34 def __str__(cls): 35 # FIXME: I have magic numbers! 36 return "<COWDict Level: %i Current Keys: %i>" % (cls.__count__, len(cls.__dict__) - 3) 37 __repr__ = __str__ 38 39 def cow(cls): 40 class C(cls): 41 __count__ = cls.__count__ + 1 42 return C 43 copy = cow 44 __call__ = cow 45 46 def __setitem__(cls, key, value): 47 if value is not None and not isinstance(value, ImmutableTypes): 48 if not isinstance(value, COWMeta): 49 cls.__hasmutable__ = True 50 key += MUTABLE 51 setattr(cls, key, value) 52 53 def __getmutable__(cls, key, readonly=False): 54 nkey = key + MUTABLE 55 try: 56 return cls.__dict__[nkey] 57 except KeyError: 58 pass 59 60 value = getattr(cls, nkey) 61 if readonly: 62 return value 63 64 if not cls.__warn__ is False and not isinstance(value, COWMeta): 65 print("Warning: Doing a copy because %s is a mutable type." % key, file=cls.__warn__) 66 try: 67 value = value.copy() 68 except AttributeError as e: 69 value = copy.copy(value) 70 setattr(cls, nkey, value) 71 return value 72 73 __getmarker__ = [] 74 def __getreadonly__(cls, key, default=__getmarker__): 75 """\ 76 Get a value (even if mutable) which you promise not to change. 77 """ 78 return cls.__getitem__(key, default, True) 79 80 def __getitem__(cls, key, default=__getmarker__, readonly=False): 81 try: 82 try: 83 value = getattr(cls, key) 84 except AttributeError: 85 value = cls.__getmutable__(key, readonly) 86 87 # This is for values which have been deleted 88 if value is cls.__marker__: 89 raise AttributeError("key %s does not exist." % key) 90 91 return value 92 except AttributeError as e: 93 if not default is cls.__getmarker__: 94 return default 95 96 raise KeyError(str(e)) 97 98 def __delitem__(cls, key): 99 cls.__setitem__(key, cls.__marker__) 100 101 def __revertitem__(cls, key): 102 if key not in cls.__dict__: 103 key += MUTABLE 104 delattr(cls, key) 105 106 def __contains__(cls, key): 107 return cls.has_key(key) 108 109 def has_key(cls, key): 110 value = cls.__getreadonly__(key, cls.__marker__) 111 if value is cls.__marker__: 112 return False 113 return True 114 115 def iter(cls, type, readonly=False): 116 for key in dir(cls): 117 if key.startswith("__"): 118 continue 119 120 if key.endswith(MUTABLE): 121 key = key[:-len(MUTABLE)] 122 123 if type == "keys": 124 yield key 125 126 try: 127 if readonly: 128 value = cls.__getreadonly__(key) 129 else: 130 value = cls[key] 131 except KeyError: 132 continue 133 134 if type == "values": 135 yield value 136 if type == "items": 137 yield (key, value) 138 return 139 140 def iterkeys(cls): 141 return cls.iter("keys") 142 def itervalues(cls, readonly=False): 143 if not cls.__warn__ is False and cls.__hasmutable__ and readonly is False: 144 print("Warning: If you arn't going to change any of the values call with True.", file=cls.__warn__) 145 return cls.iter("values", readonly) 146 def iteritems(cls, readonly=False): 147 if not cls.__warn__ is False and cls.__hasmutable__ and readonly is False: 148 print("Warning: If you arn't going to change any of the values call with True.", file=cls.__warn__) 149 return cls.iter("items", readonly) 150 151class COWSetMeta(COWDictMeta): 152 def __str__(cls): 153 # FIXME: I have magic numbers! 154 return "<COWSet Level: %i Current Keys: %i>" % (cls.__count__, len(cls.__dict__) -3) 155 __repr__ = __str__ 156 157 def cow(cls): 158 class C(cls): 159 __count__ = cls.__count__ + 1 160 return C 161 162 def add(cls, value): 163 COWDictMeta.__setitem__(cls, repr(hash(value)), value) 164 165 def remove(cls, value): 166 COWDictMeta.__delitem__(cls, repr(hash(value))) 167 168 def __in__(cls, value): 169 return repr(hash(value)) in COWDictMeta 170 171 def iterkeys(cls): 172 raise TypeError("sets don't have keys") 173 174 def iteritems(cls): 175 raise TypeError("sets don't have 'items'") 176 177# These are the actual classes you use! 178class COWDictBase(object, metaclass = COWDictMeta): 179 __count__ = 0 180 181class COWSetBase(object, metaclass = COWSetMeta): 182 __count__ = 0 183 184if __name__ == "__main__": 185 import sys 186 COWDictBase.__warn__ = sys.stderr 187 a = COWDictBase() 188 print("a", a) 189 190 a['a'] = 'a' 191 a['b'] = 'b' 192 a['dict'] = {} 193 194 b = a.copy() 195 print("b", b) 196 b['c'] = 'b' 197 198 print() 199 200 print("a", a) 201 for x in a.iteritems(): 202 print(x) 203 print("--") 204 print("b", b) 205 for x in b.iteritems(): 206 print(x) 207 print() 208 209 b['dict']['a'] = 'b' 210 b['a'] = 'c' 211 212 print("a", a) 213 for x in a.iteritems(): 214 print(x) 215 print("--") 216 print("b", b) 217 for x in b.iteritems(): 218 print(x) 219 print() 220 221 try: 222 b['dict2'] 223 except KeyError as e: 224 print("Okay!") 225 226 a['set'] = COWSetBase() 227 a['set'].add("o1") 228 a['set'].add("o1") 229 a['set'].add("o2") 230 231 print("a", a) 232 for x in a['set'].itervalues(): 233 print(x) 234 print("--") 235 print("b", b) 236 for x in b['set'].itervalues(): 237 print(x) 238 print() 239 240 b['set'].add('o3') 241 242 print("a", a) 243 for x in a['set'].itervalues(): 244 print(x) 245 print("--") 246 print("b", b) 247 for x in b['set'].itervalues(): 248 print(x) 249 print() 250 251 a['set2'] = set() 252 a['set2'].add("o1") 253 a['set2'].add("o1") 254 a['set2'].add("o2") 255 256 print("a", a) 257 for x in a.iteritems(): 258 print(x) 259 print("--") 260 print("b", b) 261 for x in b.iteritems(readonly=True): 262 print(x) 263 print() 264 265 del b['b'] 266 try: 267 print(b['b']) 268 except KeyError: 269 print("Yay! deleted key raises error") 270 271 if 'b' in b: 272 print("Boo!") 273 else: 274 print("Yay - has_key with delete works!") 275 276 print("a", a) 277 for x in a.iteritems(): 278 print(x) 279 print("--") 280 print("b", b) 281 for x in b.iteritems(readonly=True): 282 print(x) 283 print() 284 285 b.__revertitem__('b') 286 287 print("a", a) 288 for x in a.iteritems(): 289 print(x) 290 print("--") 291 print("b", b) 292 for x in b.iteritems(readonly=True): 293 print(x) 294 print() 295 296 b.__revertitem__('dict') 297 print("a", a) 298 for x in a.iteritems(): 299 print(x) 300 print("--") 301 print("b", b) 302 for x in b.iteritems(readonly=True): 303 print(x) 304 print() 305