blob: 76bc08a3ea1cecc732d3956b3bc6a20d66672e69 [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 Williams92b42cb2022-09-03 06:53:57 -05006# SPDX-License-Identifier: GPL-2.0-only
7#
Andrew Geisslerc9f78652020-09-18 14:11:35 -05008# Please Note:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05009# Be careful when using mutable types (ie Dict and Lists) - operations involving these are SLOW.
10# Assign a file to __warn__ to get warnings about slow operations.
11#
12
Patrick Williamsc0f7c042017-02-23 20:41:17 -060013
Patrick Williamsc124f4f2015-09-15 14:41:29 -050014import copy
Andrew Geisslerc9f78652020-09-18 14:11:35 -050015
Patrick Williamsc124f4f2015-09-15 14:41:29 -050016ImmutableTypes = (
Patrick Williamsc124f4f2015-09-15 14:41:29 -050017 bool,
18 complex,
19 float,
20 int,
Patrick Williamsc124f4f2015-09-15 14:41:29 -050021 tuple,
22 frozenset,
Patrick Williamsc0f7c042017-02-23 20:41:17 -060023 str
Patrick Williamsc124f4f2015-09-15 14:41:29 -050024)
25
26MUTABLE = "__mutable__"
27
Andrew Geisslerc9f78652020-09-18 14:11:35 -050028
Patrick Williamsc124f4f2015-09-15 14:41:29 -050029class COWMeta(type):
30 pass
31
Andrew Geisslerc9f78652020-09-18 14:11:35 -050032
Patrick Williamsc124f4f2015-09-15 14:41:29 -050033class COWDictMeta(COWMeta):
34 __warn__ = False
35 __hasmutable__ = False
36 __marker__ = tuple()
37
38 def __str__(cls):
39 # FIXME: I have magic numbers!
40 return "<COWDict Level: %i Current Keys: %i>" % (cls.__count__, len(cls.__dict__) - 3)
Andrew Geisslerc9f78652020-09-18 14:11:35 -050041
Patrick Williamsc124f4f2015-09-15 14:41:29 -050042 __repr__ = __str__
43
44 def cow(cls):
45 class C(cls):
46 __count__ = cls.__count__ + 1
Andrew Geisslerc9f78652020-09-18 14:11:35 -050047
Patrick Williamsc124f4f2015-09-15 14:41:29 -050048 return C
Andrew Geisslerc9f78652020-09-18 14:11:35 -050049
Patrick Williamsc124f4f2015-09-15 14:41:29 -050050 copy = cow
51 __call__ = cow
52
53 def __setitem__(cls, key, value):
Patrick Williamsc0f7c042017-02-23 20:41:17 -060054 if value is not None and not isinstance(value, ImmutableTypes):
Patrick Williamsc124f4f2015-09-15 14:41:29 -050055 if not isinstance(value, COWMeta):
56 cls.__hasmutable__ = True
57 key += MUTABLE
58 setattr(cls, key, value)
59
60 def __getmutable__(cls, key, readonly=False):
61 nkey = key + MUTABLE
62 try:
63 return cls.__dict__[nkey]
64 except KeyError:
65 pass
66
67 value = getattr(cls, nkey)
68 if readonly:
69 return value
70
71 if not cls.__warn__ is False and not isinstance(value, COWMeta):
72 print("Warning: Doing a copy because %s is a mutable type." % key, file=cls.__warn__)
73 try:
74 value = value.copy()
75 except AttributeError as e:
76 value = copy.copy(value)
77 setattr(cls, nkey, value)
78 return value
79
80 __getmarker__ = []
Andrew Geisslerc9f78652020-09-18 14:11:35 -050081
Patrick Williamsc124f4f2015-09-15 14:41:29 -050082 def __getreadonly__(cls, key, default=__getmarker__):
Andrew Geisslerc9f78652020-09-18 14:11:35 -050083 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -050084 Get a value (even if mutable) which you promise not to change.
85 """
86 return cls.__getitem__(key, default, True)
87
88 def __getitem__(cls, key, default=__getmarker__, readonly=False):
89 try:
90 try:
91 value = getattr(cls, key)
92 except AttributeError:
93 value = cls.__getmutable__(key, readonly)
94
95 # This is for values which have been deleted
96 if value is cls.__marker__:
97 raise AttributeError("key %s does not exist." % key)
98
99 return value
100 except AttributeError as e:
101 if not default is cls.__getmarker__:
102 return default
103
104 raise KeyError(str(e))
105
106 def __delitem__(cls, key):
107 cls.__setitem__(key, cls.__marker__)
108
109 def __revertitem__(cls, key):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600110 if key not in cls.__dict__:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500111 key += MUTABLE
112 delattr(cls, key)
113
114 def __contains__(cls, key):
115 return cls.has_key(key)
116
117 def has_key(cls, key):
118 value = cls.__getreadonly__(key, cls.__marker__)
119 if value is cls.__marker__:
120 return False
121 return True
122
123 def iter(cls, type, readonly=False):
124 for key in dir(cls):
125 if key.startswith("__"):
126 continue
127
128 if key.endswith(MUTABLE):
129 key = key[:-len(MUTABLE)]
130
131 if type == "keys":
132 yield key
133
134 try:
135 if readonly:
136 value = cls.__getreadonly__(key)
137 else:
138 value = cls[key]
139 except KeyError:
140 continue
141
142 if type == "values":
143 yield value
144 if type == "items":
145 yield (key, value)
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800146 return
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500147
148 def iterkeys(cls):
149 return cls.iter("keys")
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500150
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500151 def itervalues(cls, readonly=False):
152 if not cls.__warn__ is False and cls.__hasmutable__ and readonly is False:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500153 print("Warning: If you aren't going to change any of the values call with True.", file=cls.__warn__)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500154 return cls.iter("values", readonly)
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500155
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500156 def iteritems(cls, readonly=False):
157 if not cls.__warn__ is False and cls.__hasmutable__ and readonly is False:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500158 print("Warning: If you aren't going to change any of the values call with True.", file=cls.__warn__)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500159 return cls.iter("items", readonly)
160
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500161
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500162class COWSetMeta(COWDictMeta):
163 def __str__(cls):
164 # FIXME: I have magic numbers!
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500165 return "<COWSet Level: %i Current Keys: %i>" % (cls.__count__, len(cls.__dict__) - 3)
166
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500167 __repr__ = __str__
168
169 def cow(cls):
170 class C(cls):
171 __count__ = cls.__count__ + 1
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500172
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500173 return C
174
175 def add(cls, value):
176 COWDictMeta.__setitem__(cls, repr(hash(value)), value)
177
178 def remove(cls, value):
179 COWDictMeta.__delitem__(cls, repr(hash(value)))
180
181 def __in__(cls, value):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600182 return repr(hash(value)) in COWDictMeta
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500183
184 def iterkeys(cls):
185 raise TypeError("sets don't have keys")
186
187 def iteritems(cls):
188 raise TypeError("sets don't have 'items'")
189
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500190
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500191# These are the actual classes you use!
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500192class COWDictBase(metaclass=COWDictMeta):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500193 __count__ = 0
194
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500195
196class COWSetBase(metaclass=COWSetMeta):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500197 __count__ = 0