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