##// END OF EJS Templates
Address review comments @takluyver
Jonathan Frederic -
Show More
@@ -1,289 +1,292 b''
1 """Contains eventful dict and list implementations."""
1 """Contains eventful dict and list implementations."""
2
2
3 # void function used as a callback placeholder.
4 def _void(*p, **k): return None
5
3 class EventfulDict(dict):
6 class EventfulDict(dict):
4 """Eventful dictionary.
7 """Eventful dictionary.
5
8
6 This class inherits from the Python intrinsic dictionary class, dict. It
9 This class inherits from the Python intrinsic dictionary class, dict. It
7 adds events to the get, set, and del actions and optionally allows you to
10 adds events to the get, set, and del actions and optionally allows you to
8 intercept and cancel these actions. The eventfulness isn't recursive. In
11 intercept and cancel these actions. The eventfulness isn't recursive. In
9 other words, if you add a dict as a child, the events of that dict won't be
12 other words, if you add a dict as a child, the events of that dict won't be
10 listened to. If you find you need something recursive, listen to the `add`
13 listened to. If you find you need something recursive, listen to the `add`
11 and `set` methods, and then cancel `dict` values from being set, and instead
14 and `set` methods, and then cancel `dict` values from being set, and instead
12 set `EventfulDict`s that wrap those `dict`s. Then you can wire the events
15 set `EventfulDict`s that wrap those `dict`s. Then you can wire the events
13 to the same handlers if necessary.
16 to the same handlers if necessary.
14
17
15 See the on_add, on_set, and on_del methods for registering an event
18 See the on_add, on_set, and on_del methods for registering an event
16 handler."""
19 handler."""
17
20
18 def __init__(self, *args, **kwargs):
21 def __init__(self, *args, **kwargs):
19 """Public constructor"""
22 """Public constructor"""
20 self._add_callback = None
23 self._add_callback = _void
21 self._del_callback = None
24 self._del_callback = _void
22 self._set_callback = None
25 self._set_callback = _void
23 dict.__init__(self, *args, **kwargs)
26 dict.__init__(self, *args, **kwargs)
24
27
25 def on_add(self, callback):
28 def on_add(self, callback):
26 """Register a callback for when an item is added to the dict.
29 """Register a callback for when an item is added to the dict.
27
30
28 Allows the listener to detect when items are added to the dictionary and
31 Allows the listener to detect when items are added to the dictionary and
29 optionally cancel the addition.
32 optionally cancel the addition.
30
33
31 callback: callable or None
34 callback: callable or None
32 If you want to ignore the addition event, pass None as the callback.
35 If you want to ignore the addition event, pass None as the callback.
33 The callback should have a signature of callback(key, value). The
36 The callback should have a signature of callback(key, value). The
34 callback should return a boolean True if the additon should be
37 callback should return a boolean True if the additon should be
35 canceled, False or None otherwise."""
38 canceled, False or None otherwise."""
36 self._add_callback = callback
39 if callable(callback):
40 self._add_callback = callback
41 else:
42 self._add_callback = _void
37
43
38 def on_del(self, callback):
44 def on_del(self, callback):
39 """Register a callback for when an item is deleted from the dict.
45 """Register a callback for when an item is deleted from the dict.
40
46
41 Allows the listener to detect when items are deleted from the dictionary
47 Allows the listener to detect when items are deleted from the dictionary
42 and optionally cancel the deletion.
48 and optionally cancel the deletion.
43
49
44 callback: callable or None
50 callback: callable or None
45 If you want to ignore the deletion event, pass None as the callback.
51 If you want to ignore the deletion event, pass None as the callback.
46 The callback should have a signature of callback(key). The
52 The callback should have a signature of callback(key). The
47 callback should return a boolean True if the deletion should be
53 callback should return a boolean True if the deletion should be
48 canceled, False or None otherwise."""
54 canceled, False or None otherwise."""
49 self._del_callback = callback
55 if callable(callback):
56 self._del_callback = callback
57 else:
58 self._del_callback = _void
50
59
51 def on_set(self, callback):
60 def on_set(self, callback):
52 """Register a callback for when an item is changed in the dict.
61 """Register a callback for when an item is changed in the dict.
53
62
54 Allows the listener to detect when items are changed in the dictionary
63 Allows the listener to detect when items are changed in the dictionary
55 and optionally cancel the change.
64 and optionally cancel the change.
56
65
57 callback: callable or None
66 callback: callable or None
58 If you want to ignore the change event, pass None as the callback.
67 If you want to ignore the change event, pass None as the callback.
59 The callback should have a signature of callback(key, value). The
68 The callback should have a signature of callback(key, value). The
60 callback should return a boolean True if the change should be
69 callback should return a boolean True if the change should be
61 canceled, False or None otherwise."""
70 canceled, False or None otherwise."""
62 self._set_callback = callback
71 if callable(callback):
63
72 self._set_callback = callback
64 def _can_add(self, key, value):
65 """Check if the item can be added to the dict."""
66 if callable(self._add_callback):
67 return not bool(self._add_callback(key, value))
68 else:
69 return True
70
71 def _can_del(self, key):
72 """Check if the item can be deleted from the dict."""
73 if callable(self._del_callback):
74 return not bool(self._del_callback(key))
75 else:
76 return True
77
78 def _can_set(self, key, value):
79 """Check if the item can be changed in the dict."""
80 if callable(self._set_callback):
81 return not bool(self._set_callback(key, value))
82 else:
73 else:
83 return True
74 self._set_callback = _void
84
75
85 def pop(self, key):
76 def pop(self, key):
86 """Returns the value of an item in the dictionary and then deletes the
77 """Returns the value of an item in the dictionary and then deletes the
87 item from the dictionary."""
78 item from the dictionary."""
88 if self._can_del(key):
79 if self._can_del(key):
89 return dict.pop(self, key)
80 return dict.pop(self, key)
90 else:
81 else:
91 raise Exception('Cannot `pop`, deletion of key "{}" failed.'.format(key))
82 raise Exception('Cannot `pop`, deletion of key "{}" failed.'.format(key))
92
83
93 def popitem(self):
84 def popitem(self):
94 """Pop the next key/value pair from the dictionary."""
85 """Pop the next key/value pair from the dictionary."""
95 key = dict.keys(self)[0]
86 key = next(iter(self))
96 return key, self.pop(key)
87 return key, self.pop(key)
97
88
98 def update(self, other_dict):
89 def update(self, other_dict):
99 """Copy the key/value pairs from another dictionary into this dictionary,
90 """Copy the key/value pairs from another dictionary into this dictionary,
100 overwriting any conflicting keys in this dictionary."""
91 overwriting any conflicting keys in this dictionary."""
101 for (key, value) in other_dict.items():
92 for (key, value) in other_dict.items():
102 self[key] = value
93 self[key] = value
103
94
104 def clear(self):
95 def clear(self):
105 """Clear the dictionary."""
96 """Clear the dictionary."""
106 for key in list(self.keys()):
97 for key in list(self.keys()):
107 del self[key]
98 del self[key]
108
99
109 def __setitem__(self, key, value):
100 def __setitem__(self, key, value):
110 if (key in self and self._can_set(key, value)) or \
101 if (key in self and self._can_set(key, value)) or \
111 (key not in self and self._can_add(key, value)):
102 (key not in self and self._can_add(key, value)):
112 return dict.__setitem__(self, key, value)
103 return dict.__setitem__(self, key, value)
113
104
114 def __delitem__(self, key):
105 def __delitem__(self, key):
115 if self._can_del(key):
106 if self._can_del(key):
116 return dict.__delitem__(self, key)
107 return dict.__delitem__(self, key)
117
108
109 def _can_add(self, key, value):
110 """Check if the item can be added to the dict."""
111 return not bool(self._add_callback(key, value))
112
113 def _can_del(self, key):
114 """Check if the item can be deleted from the dict."""
115 return not bool(self._del_callback(key))
116
117 def _can_set(self, key, value):
118 """Check if the item can be changed in the dict."""
119 return not bool(self._set_callback(key, value))
120
118
121
119 class EventfulList(list):
122 class EventfulList(list):
120 """Eventful list.
123 """Eventful list.
121
124
122 This class inherits from the Python intrinsic `list` class. It adds events
125 This class inherits from the Python intrinsic `list` class. It adds events
123 that allow you to listen for actions that modify the list. You can
126 that allow you to listen for actions that modify the list. You can
124 optionally cancel the actions.
127 optionally cancel the actions.
125
128
126 See the on_del, on_set, on_insert, on_sort, and on_reverse methods for
129 See the on_del, on_set, on_insert, on_sort, and on_reverse methods for
127 registering an event handler.
130 registering an event handler.
128
131
129 Some of the method docstrings were taken from the Python documentation at
132 Some of the method docstrings were taken from the Python documentation at
130 https://docs.python.org/2/tutorial/datastructures.html"""
133 https://docs.python.org/2/tutorial/datastructures.html"""
131
134
132 def __init__(self, *pargs, **kwargs):
135 def __init__(self, *pargs, **kwargs):
133 """Public constructor"""
136 """Public constructor"""
134 self._insert_callback = None
137 self._insert_callback = _void
135 self._set_callback = None
138 self._set_callback = _void
136 self._del_callback = None
139 self._del_callback = _void
137 self._sort_callback = None
140 self._sort_callback = _void
138 self._reverse_callback = None
141 self._reverse_callback = _void
139 list.__init__(self, *pargs, **kwargs)
142 list.__init__(self, *pargs, **kwargs)
140
143
141 def on_insert(self, callback):
144 def on_insert(self, callback):
142 """Register a callback for when an item is inserted into the list.
145 """Register a callback for when an item is inserted into the list.
143
146
144 Allows the listener to detect when items are inserted into the list and
147 Allows the listener to detect when items are inserted into the list and
145 optionally cancel the insertion.
148 optionally cancel the insertion.
146
149
147 callback: callable or None
150 callback: callable or None
148 If you want to ignore the insertion event, pass None as the callback.
151 If you want to ignore the insertion event, pass None as the callback.
149 The callback should have a signature of callback(index, value). The
152 The callback should have a signature of callback(index, value). The
150 callback should return a boolean True if the insertion should be
153 callback should return a boolean True if the insertion should be
151 canceled, False or None otherwise."""
154 canceled, False or None otherwise."""
152 self._insert_callback = callback
155 if callable(callback):
156 self._insert_callback = callback
157 else:
158 self._insert_callback = _void
153
159
154 def on_del(self, callback):
160 def on_del(self, callback):
155 """Register a callback for item deletion.
161 """Register a callback for item deletion.
156
162
157 Allows the listener to detect when items are deleted from the list and
163 Allows the listener to detect when items are deleted from the list and
158 optionally cancel the deletion.
164 optionally cancel the deletion.
159
165
160 callback: callable or None
166 callback: callable or None
161 If you want to ignore the deletion event, pass None as the callback.
167 If you want to ignore the deletion event, pass None as the callback.
162 The callback should have a signature of callback(index). The
168 The callback should have a signature of callback(index). The
163 callback should return a boolean True if the deletion should be
169 callback should return a boolean True if the deletion should be
164 canceled, False or None otherwise."""
170 canceled, False or None otherwise."""
165 self._del_callback = callback
171 if callable(callback):
172 self._del_callback = callback
173 else:
174 self._del_callback = _void
166
175
167 def on_set(self, callback):
176 def on_set(self, callback):
168 """Register a callback for items are set.
177 """Register a callback for items are set.
169
178
170 Allows the listener to detect when items are set and optionally cancel
179 Allows the listener to detect when items are set and optionally cancel
171 the setting. Note, `set` is also called when one or more items are
180 the setting. Note, `set` is also called when one or more items are
172 added to the end of the list.
181 added to the end of the list.
173
182
174 callback: callable or None
183 callback: callable or None
175 If you want to ignore the set event, pass None as the callback.
184 If you want to ignore the set event, pass None as the callback.
176 The callback should have a signature of callback(index, value). The
185 The callback should have a signature of callback(index, value). The
177 callback should return a boolean True if the set should be
186 callback should return a boolean True if the set should be
178 canceled, False or None otherwise."""
187 canceled, False or None otherwise."""
179 self._set_callback = callback
188 if callable(callback):
189 self._set_callback = callback
190 else:
191 self._set_callback = _void
180
192
181 def on_reverse(self, callback):
193 def on_reverse(self, callback):
182 """Register a callback for list reversal.
194 """Register a callback for list reversal.
183
195
184 callback: callable or None
196 callback: callable or None
185 If you want to ignore the reverse event, pass None as the callback.
197 If you want to ignore the reverse event, pass None as the callback.
186 The callback should have a signature of callback(). The
198 The callback should have a signature of callback(). The
187 callback should return a boolean True if the reverse should be
199 callback should return a boolean True if the reverse should be
188 canceled, False or None otherwise."""
200 canceled, False or None otherwise."""
189 self._reverse_callback = callback
201 if callable(callback):
202 self._reverse_callback = callback
203 else:
204 self._reverse_callback = _void
190
205
191 def on_sort(self, callback):
206 def on_sort(self, callback):
192 """Register a callback for sortting of the list.
207 """Register a callback for sortting of the list.
193
208
194 callback: callable or None
209 callback: callable or None
195 If you want to ignore the sort event, pass None as the callback.
210 If you want to ignore the sort event, pass None as the callback.
196 The callback signature should match that of Python list's `.sort`
211 The callback signature should match that of Python list's `.sort`
197 method or `callback(*pargs, **kwargs)` as a catch all. The callback
212 method or `callback(*pargs, **kwargs)` as a catch all. The callback
198 should return a boolean True if the reverse should be canceled,
213 should return a boolean True if the reverse should be canceled,
199 False or None otherwise."""
214 False or None otherwise."""
200 self._sort_callback = callback
215 if callable(callback):
201
216 self._sort_callback = callback
202 def _can_insert(self, index, value):
203 """Check if the item can be inserted."""
204 if callable(self._insert_callback):
205 return not bool(self._insert_callback(index, value))
206 else:
217 else:
207 return True
218 self._sort_callback = _void
208
209 def _can_del(self, index):
210 """Check if the item can be deleted."""
211 if callable(self._del_callback):
212 return not bool(self._del_callback(index))
213 else:
214 return True
215
216 def _can_set(self, index, value):
217 """Check if the item can be set."""
218 if callable(self._set_callback):
219 return not bool(self._set_callback(index, value))
220 else:
221 return True
222
223 def _can_reverse(self):
224 """Check if the list can be reversed."""
225 if callable(self._reverse_callback):
226 return not bool(self._reverse_callback())
227 else:
228 return True
229
230 def _can_sort(self, *pargs, **kwargs):
231 """Check if the list can be sorted."""
232 if callable(self._sort_callback):
233 return not bool(self._sort_callback(*pargs, **kwargs))
234 else:
235 return True
236
219
237 def append(self, x):
220 def append(self, x):
238 """Add an item to the end of the list."""
221 """Add an item to the end of the list."""
239 self[len(self):] = [x]
222 self[len(self):] = [x]
240
223
241 def extend(self, L):
224 def extend(self, L):
242 """Extend the list by appending all the items in the given list."""
225 """Extend the list by appending all the items in the given list."""
243 self[len(self):] = L
226 self[len(self):] = L
244
227
245 def remove(self, x):
228 def remove(self, x):
246 """Remove the first item from the list whose value is x. It is an error
229 """Remove the first item from the list whose value is x. It is an error
247 if there is no such item."""
230 if there is no such item."""
248 del self[self.index(x)]
231 del self[self.index(x)]
249
232
250 def pop(self, i=None):
233 def pop(self, i=None):
251 """Remove the item at the given position in the list, and return it. If
234 """Remove the item at the given position in the list, and return it. If
252 no index is specified, a.pop() removes and returns the last item in the
235 no index is specified, a.pop() removes and returns the last item in the
253 list."""
236 list."""
254 if i is None:
237 if i is None:
255 i = len(self) - 1
238 i = len(self) - 1
256 val = self[i]
239 val = self[i]
257 del self[i]
240 del self[i]
258 return val
241 return val
259
242
260 def reverse(self):
243 def reverse(self):
261 """Reverse the elements of the list, in place."""
244 """Reverse the elements of the list, in place."""
262 if self._can_reverse():
245 if self._can_reverse():
263 list.reverse(self)
246 list.reverse(self)
264
247
265 def insert(self, index, value):
248 def insert(self, index, value):
266 """Insert an item at a given position. The first argument is the index
249 """Insert an item at a given position. The first argument is the index
267 of the element before which to insert, so a.insert(0, x) inserts at the
250 of the element before which to insert, so a.insert(0, x) inserts at the
268 front of the list, and a.insert(len(a), x) is equivalent to
251 front of the list, and a.insert(len(a), x) is equivalent to
269 a.append(x)."""
252 a.append(x)."""
270 if self._can_insert(index, value):
253 if self._can_insert(index, value):
271 list.insert(self, index, value)
254 list.insert(self, index, value)
272
255
273 def sort(self, *pargs, **kwargs):
256 def sort(self, *pargs, **kwargs):
274 """Sort the items of the list in place (the arguments can be used for
257 """Sort the items of the list in place (the arguments can be used for
275 sort customization, see Python's sorted() for their explanation)."""
258 sort customization, see Python's sorted() for their explanation)."""
276 if self._can_sort(*pargs, **kwargs):
259 if self._can_sort(*pargs, **kwargs):
277 list.sort(self, *pargs, **kwargs)
260 list.sort(self, *pargs, **kwargs)
278
261
279 def __delitem__(self, index):
262 def __delitem__(self, index):
280 if self._can_del(index):
263 if self._can_del(index):
281 list.__delitem__(self, index)
264 list.__delitem__(self, index)
282
265
283 def __setitem__(self, index, value):
266 def __setitem__(self, index, value):
284 if self._can_set(index, value):
267 if self._can_set(index, value):
285 list.__setitem__(self, index, value)
268 list.__setitem__(self, index, value)
286
269
287 def __setslice__(self, start, end, value):
270 def __setslice__(self, start, end, value):
288 if self._can_set(slice(start, end), value):
271 if self._can_set(slice(start, end), value):
289 list.__setslice__(self, start, end, value)
272 list.__setslice__(self, start, end, value)
273
274 def _can_insert(self, index, value):
275 """Check if the item can be inserted."""
276 return not bool(self._insert_callback(index, value))
277
278 def _can_del(self, index):
279 """Check if the item can be deleted."""
280 return not bool(self._del_callback(index))
281
282 def _can_set(self, index, value):
283 """Check if the item can be set."""
284 return not bool(self._set_callback(index, value))
285
286 def _can_reverse(self):
287 """Check if the list can be reversed."""
288 return not bool(self._reverse_callback())
289
290 def _can_sort(self, *pargs, **kwargs):
291 """Check if the list can be sorted."""
292 return not bool(self._sort_callback(*pargs, **kwargs))
General Comments 0
You need to be logged in to leave comments. Login now