blob: bc20ce38e2e3e7ca94cbfd7d9b813faaa6755974 [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
Patrick Williamsc124f4f2015-09-15 14:41:29 -050013ImmutableTypes = (
Patrick Williamsc124f4f2015-09-15 14:41:29 -050014 bool,
15 complex,
16 float,
17 int,
Patrick Williamsc124f4f2015-09-15 14:41:29 -050018 tuple,
19 frozenset,
Patrick Williamsc0f7c042017-02-23 20:41:17 -060020 str
Patrick Williamsc124f4f2015-09-15 14:41:29 -050021)
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):
Patrick Williamsc0f7c042017-02-23 20:41:17 -060046 if value is not None and not isinstance(value, ImmutableTypes):
Patrick Williamsc124f4f2015-09-15 14:41:29 -050047 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):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600101 if key not in cls.__dict__:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500102 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)
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800137 return
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500138
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):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600168 return repr(hash(value)) in COWDictMeta
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500169
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!
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600177class COWDictBase(object, metaclass = COWDictMeta):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500178 __count__ = 0
179
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600180class COWSetBase(object, metaclass = COWSetMeta):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500181 __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)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500200 for x in a.iteritems():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500201 print(x)
202 print("--")
203 print("b", b)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500204 for x in b.iteritems():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500205 print(x)
206 print()
207
208 b['dict']['a'] = 'b'
209 b['a'] = 'c'
210
211 print("a", a)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500212 for x in a.iteritems():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500213 print(x)
214 print("--")
215 print("b", b)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500216 for x in b.iteritems():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500217 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)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500231 for x in a['set'].itervalues():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500232 print(x)
233 print("--")
234 print("b", b)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500235 for x in b['set'].itervalues():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500236 print(x)
237 print()
238
239 b['set'].add('o3')
240
241 print("a", a)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500242 for x in a['set'].itervalues():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500243 print(x)
244 print("--")
245 print("b", b)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500246 for x in b['set'].itervalues():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500247 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)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500256 for x in a.iteritems():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500257 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
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600270 if 'b' in b:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500271 print("Boo!")
272 else:
273 print("Yay - has_key with delete works!")
274
275 print("a", a)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500276 for x in a.iteritems():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500277 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)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500287 for x in a.iteritems():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500288 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)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500297 for x in a.iteritems():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500298 print(x)
299 print("--")
300 print("b", b)
301 for x in b.iteritems(readonly=True):
302 print(x)
303 print()