blob: d26e98195151119612e32bbc75dd29f3aab8e90c [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001#
2# This is a copy on write dictionary and set which abuses classes to try and be nice and fast.
3#
Brad Bishopd7bf8c12018-02-25 22:55:05 -05004# Copyright (C) 2006 Tim Ansell
Patrick Williamsc124f4f2015-09-15 14:41:29 -05005#
Patrick Williamsc124f4f2015-09-15 14:41:29 -05006#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
Patrick Williamsc0f7c042017-02-23 20:41:17 -060011
Patrick Williamsc124f4f2015-09-15 14:41:29 -050012import copy
13import types
14ImmutableTypes = (
Patrick Williamsc124f4f2015-09-15 14:41:29 -050015 bool,
16 complex,
17 float,
18 int,
Patrick Williamsc124f4f2015-09-15 14:41:29 -050019 tuple,
20 frozenset,
Patrick Williamsc0f7c042017-02-23 20:41:17 -060021 str
Patrick Williamsc124f4f2015-09-15 14:41:29 -050022)
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):
Patrick Williamsc0f7c042017-02-23 20:41:17 -060047 if value is not None and not isinstance(value, ImmutableTypes):
Patrick Williamsc124f4f2015-09-15 14:41:29 -050048 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):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600102 if key not in cls.__dict__:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500103 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)
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800138 return
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500139
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):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600169 return repr(hash(value)) in COWDictMeta
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500170
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!
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600178class COWDictBase(object, metaclass = COWDictMeta):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500179 __count__ = 0
180
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600181class COWSetBase(object, metaclass = COWSetMeta):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500182 __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)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500201 for x in a.iteritems():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500202 print(x)
203 print("--")
204 print("b", b)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500205 for x in b.iteritems():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500206 print(x)
207 print()
208
209 b['dict']['a'] = 'b'
210 b['a'] = 'c'
211
212 print("a", a)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500213 for x in a.iteritems():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500214 print(x)
215 print("--")
216 print("b", b)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500217 for x in b.iteritems():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500218 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)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500232 for x in a['set'].itervalues():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500233 print(x)
234 print("--")
235 print("b", b)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500236 for x in b['set'].itervalues():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500237 print(x)
238 print()
239
240 b['set'].add('o3')
241
242 print("a", a)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500243 for x in a['set'].itervalues():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500244 print(x)
245 print("--")
246 print("b", b)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500247 for x in b['set'].itervalues():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500248 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)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500257 for x in a.iteritems():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500258 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
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600271 if 'b' in b:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500272 print("Boo!")
273 else:
274 print("Yay - has_key with delete works!")
275
276 print("a", a)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500277 for x in a.iteritems():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500278 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)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500288 for x in a.iteritems():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500289 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)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500298 for x in a.iteritems():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500299 print(x)
300 print("--")
301 print("b", b)
302 for x in b.iteritems(readonly=True):
303 print(x)
304 print()