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