##// END OF EJS Templates
First draft of refactored ipstruct.py.
Brian Granger -
Show More
@@ -111,7 +111,7 b' class ColorScheme:'
111 """Return a full copy of the object, optionally renaming it."""
111 """Return a full copy of the object, optionally renaming it."""
112 if name is None:
112 if name is None:
113 name = self.name
113 name = self.name
114 return ColorScheme(name,self.colors.__dict__)
114 return ColorScheme(name, self.colors.dict())
115
115
116 class ColorSchemeTable(dict):
116 class ColorSchemeTable(dict):
117 """General class to handle tables of color schemes.
117 """General class to handle tables of color schemes.
@@ -11,12 +11,13 b''
11
11
12 __all__ = ['Struct']
12 __all__ = ['Struct']
13
13
14 import inspect
14 import types
15 import types
15 import pprint
16 import pprint
16
17
17 from IPython.utils.genutils import list2dict2
18 from IPython.utils.genutils import list2dict2
18
19
19 class Struct:
20 class Struct(object):
20 """Class to mimic C structs but also provide convenient dictionary-like
21 """Class to mimic C structs but also provide convenient dictionary-like
21 functionality.
22 functionality.
22
23
@@ -105,222 +106,224 b' class Struct:'
105 'merge popitem setdefault update values '
106 'merge popitem setdefault update values '
106 '__make_dict __dict_invert ').split()
107 '__make_dict __dict_invert ').split()
107
108
108 def __init__(self,dict=None,**kw):
109 def __init__(self,data=None,**kw):
109 """Initialize with a dictionary, another Struct, or by giving
110 """Initialize with a dictionary, another Struct, or data.
110 explicitly the list of attributes.
111
111
112 Both can be used, but the dictionary must come first:
112 Parameters
113 Struct(dict), Struct(k1=v1,k2=v2) or Struct(dict,k1=v1,k2=v2).
113 ----------
114 data : dict, Struct
115 Initialize with this data.
116 kw : dict
117 Initialize with key, value pairs.
118
119 Examples
120 --------
121
114 """
122 """
115 self.__dict__['__allownew'] = True
123 object.__setattr__(self, '_allownew', True)
116 if dict is None:
124 object.__setattr__(self, '_data',{})
117 dict = {}
125 if data is None:
118 if isinstance(dict,Struct):
126 data = {}
119 dict = dict.dict()
127 if isinstance(data, Struct):
120 elif dict and type(dict) is not types.DictType:
128 data = data.dict()
121 raise TypeError,\
129 elif data and not isinstance(data, dict):
122 'Initialize with a dictionary or key=val pairs.'
130 raise TypeError('initialize with a dict, Struct or key=val pairs')
123 dict.update(kw)
131 data.update(kw)
124 # do the updating by hand to guarantee that we go through the
132 # do the updating by hand to guarantee that we go through the
125 # safety-checked __setitem__
133 # safety-checked __setitem__
126 for k,v in dict.items():
134 for k, v in data.items():
127 self[k] = v
135 self[k] = v
128
129
136
130 def __setitem__(self,key,value):
137 def __setitem__(self, key, value):
131 """Used when struct[key] = val calls are made."""
138 """Used when struct[key] = val calls are made."""
132 if key in Struct.__protected:
139 if isinstance(key, str):
133 raise KeyError,'Key '+`key`+' is a protected key of class Struct.'
140 # I can't simply call hasattr here because it calls getattr, which
134 if not self['__allownew'] and key not in self.__dict__:
141 # calls self.__getattr__, which returns True for keys in
142 # self._data. But I only want keys in the class and in
143 # self.__dict__
144 if key in self.__dict__ or hasattr(Struct, key):
145 raise KeyError(
146 'key %s is a protected key of class Struct.' % key
147 )
148 if not self._allownew and key not in self._data:
135 raise KeyError(
149 raise KeyError(
136 "Can't create unknown attribute %s - Check for typos, or use allow_new_attr to create new attributes!" %
150 "can't create unknown attribute %s. Check for typos, or use allow_new_attr" % key)
137 key)
151 self._data[key] = value
138
139 self.__dict__[key] = value
140
152
141 def __setattr__(self, key, value):
153 def __setattr__(self, key, value):
142 """Used when struct.key = val calls are made."""
154 self.__setitem__(key, value)
143 self.__setitem__(key,value)
155
156 def __getattr__(self, key):
157 try:
158 result = self._data[key]
159 except KeyError:
160 raise AttributeError(key)
161 else:
162 return result
163
164 def __getitem__(self, key):
165 return self._data[key]
144
166
145 def __str__(self):
167 def __str__(self):
146 """Gets called by print."""
168 return 'Struct('+ pprint.pformat(self._data)+')'
147
148 return 'Struct('+ pprint.pformat(self.__dict__)+')'
149
169
150 def __repr__(self):
170 def __repr__(self):
151 """Gets called by repr.
152
153 A Struct can be recreated with S_new=eval(repr(S_old))."""
154 return self.__str__()
171 return self.__str__()
155
172
156 def __getitem__(self,key):
173 def __contains__(self, key):
157 """Allows struct[key] access."""
174 return key in self._data
158 return self.__dict__[key]
159
160 def __contains__(self,key):
161 """Allows use of the 'in' operator.
162
163 Examples:
164 >>> s = Struct(x=1)
165 >>> 'x' in s
166 True
167 >>> 'y' in s
168 False
169 >>> s[4] = None
170 >>> 4 in s
171 True
172 >>> s.z = None
173 >>> 'z' in s
174 True
175 """
176 return key in self.__dict__
177
175
178 def __iadd__(self,other):
176 def __iadd__(self, other):
179 """S += S2 is a shorthand for S.merge(S2)."""
177 """S += S2 is a shorthand for S.merge(S2)."""
180 self.merge(other)
178 self.merge(other)
181 return self
179 return self
182
180
183 def __add__(self,other):
181 def __add__(self,other):
184 """S + S2 -> New Struct made form S and S.merge(S2)"""
182 """S + S2 -> New Struct made from S.merge(S2)"""
185 Sout = self.copy()
183 Sout = self.copy()
186 Sout.merge(other)
184 Sout.merge(other)
187 return Sout
185 return Sout
188
186
189 def __sub__(self,other):
187 def __sub__(self,other):
190 """Return S1-S2, where all keys in S2 have been deleted (if present)
188 """Out of place remove keys from self that are in other."""
191 from S1."""
192 Sout = self.copy()
189 Sout = self.copy()
193 Sout -= other
190 Sout -= other
194 return Sout
191 return Sout
195
192
196 def __isub__(self,other):
193 def __isub__(self,other):
197 """Do in place S = S - S2, meaning all keys in S2 have been deleted
194 """Inplace remove keys from self that are in other."""
198 (if present) from S1."""
199
200 for k in other.keys():
195 for k in other.keys():
201 if self.has_key(k):
196 if self.has_key(k):
202 del self.__dict__[k]
197 del self._data[k]
203
198
204 def __make_dict(self,__loc_data__,**kw):
199 def __make_dict(self,__loc_data__,**kw):
205 "Helper function for update and merge. Return a dict from data."
200 """Helper function for update and merge. Return a dict from data.
206
201 """
207 if __loc_data__ == None:
202 if __loc_data__ == None:
208 dict = {}
203 data = {}
209 elif type(__loc_data__) is types.DictType:
204 elif isinstance(__loc_data__, dict):
210 dict = __loc_data__
205 data = __loc_data__
211 elif isinstance(__loc_data__,Struct):
206 elif isinstance(__loc_data__, Struct):
212 dict = __loc_data__.__dict__
207 data = __loc_data__._data
213 else:
208 else:
214 raise TypeError, 'Update with a dict, a Struct or key=val pairs.'
209 raise TypeError('update with a dict, Struct or key=val pairs')
215 if kw:
210 if kw:
216 dict.update(kw)
211 data.update(kw)
217 return dict
212 return data
218
213
219 def __dict_invert(self,dict):
214 def __dict_invert(self, data):
220 """Helper function for merge. Takes a dictionary whose values are
215 """Helper function for merge.
221 lists and returns a dict. with the elements of each list as keys and
216
222 the original keys as values."""
217 Takes a dictionary whose values are lists and returns a dict with
223
218 the elements of each list as keys and the original keys as values.
219 """
224 outdict = {}
220 outdict = {}
225 for k,lst in dict.items():
221 for k,lst in data.items():
226 if type(lst) is types.StringType:
222 if isinstance(lst, str):
227 lst = lst.split()
223 lst = lst.split()
228 for entry in lst:
224 for entry in lst:
229 outdict[entry] = k
225 outdict[entry] = k
230 return outdict
226 return outdict
231
227
232 def clear(self):
228 def clear(self):
233 """Clear all attributes."""
229 """Clear all attributes."""
234 self.__dict__.clear()
230 self._data.clear()
235
231
236 def copy(self):
232 def copy(self):
237 """Return a (shallow) copy of a Struct."""
233 """Return a (shallow) copy of a Struct."""
238 return Struct(self.__dict__.copy())
234 return Struct(self._data.copy())
239
235
240 def dict(self):
236 def dict(self):
241 """Return the Struct's dictionary."""
237 """Return the Struct's dictionary."""
242 return self.__dict__
238 return self._data
243
239
244 def dictcopy(self):
240 def dictcopy(self):
245 """Return a (shallow) copy of the Struct's dictionary."""
241 """Return a (shallow) copy of the Struct's dictionary."""
246 return self.__dict__.copy()
242 return self._data.copy()
247
243
248 def popitem(self):
244 def popitem(self):
249 """S.popitem() -> (k, v), remove and return some (key, value) pair as
245 """Return (key, value) tuple and remove from Struct.
250 a 2-tuple; but raise KeyError if S is empty."""
246
251 return self.__dict__.popitem()
247 If key is not present raise KeyError.
252
248 """
249 return self._data.popitem()
250
253 def update(self,__loc_data__=None,**kw):
251 def update(self,__loc_data__=None,**kw):
254 """Update (merge) with data from another Struct or from a dictionary.
252 """Update (merge) with data from another Struct or dict.
255 Optionally, one or more key=value pairs can be given at the end for
253
256 direct update."""
254 Parameters
257
255 ----------
256 __loc_data : dict, Struct
257 The new data to add to self.
258 kw : dict
259 Key, value pairs to add to self.
260 """
258 # The funny name __loc_data__ is to prevent a common variable name
261 # The funny name __loc_data__ is to prevent a common variable name
259 # which could be a fieled of a Struct to collide with this
262 # which could be a fieled of a Struct to collide with this
260 # parameter. The problem would arise if the function is called with a
263 # parameter. The problem would arise if the function is called with a
261 # keyword with this same name that a user means to add as a Struct
264 # keyword with this same name that a user means to add as a Struct
262 # field.
265 # field.
263 newdict = Struct.__make_dict(self,__loc_data__,**kw)
266 newdict = self.__make_dict(__loc_data__, **kw)
264 for k,v in newdict.iteritems():
267 for k, v in newdict.iteritems():
265 self[k] = v
268 self[k] = v
266
269
267 def merge(self,__loc_data__=None,__conflict_solve=None,**kw):
270 def merge(self, __loc_data__=None, __conflict_solve=None, **kw):
268 """S.merge(data,conflict,k=v1,k=v2,...) -> merge data and k=v into S.
271 """S.merge(data,conflict,k=v1,k=v2,...) -> merge data and k=v into S.
269
272
270 This is similar to update(), but much more flexible. First, a dict is
273 This is similar to update(), but much more flexible. First, a dict is
271 made from data+key=value pairs. When merging this dict with the Struct
274 made from data+key=value pairs. When merging this dict with the Struct
272 S, the optional dictionary 'conflict' is used to decide what to do.
275 S, the optional dictionary 'conflict' is used to decide what to do.
273
276
274 If conflict is not given, the default behavior is to preserve any keys
277 If conflict is not given, the default behavior is to preserve any keys
275 with their current value (the opposite of the update method's
278 with their current value (the opposite of the update method's
276 behavior).
279 behavior).
277
280
278 conflict is a dictionary of binary functions which will be used to
281 conflict is a dictionary of binary functions which will be used to
279 solve key conflicts. It must have the following structure:
282 solve key conflicts. It must have the following structure:
280
283
281 conflict == { fn1 : [Skey1,Skey2,...], fn2 : [Skey3], etc }
284 conflict == { fn1 : [Skey1,Skey2,...], fn2 : [Skey3], etc }
282
285
283 Values must be lists or whitespace separated strings which are
286 Values must be lists or whitespace separated strings which are
284 automatically converted to lists of strings by calling string.split().
287 automatically converted to lists of strings by calling string.split().
285
288
286 Each key of conflict is a function which defines a policy for
289 Each key of conflict is a function which defines a policy for
287 resolving conflicts when merging with the input data. Each fn must be
290 resolving conflicts when merging with the input data. Each fn must be
288 a binary function which returns the desired outcome for a key
291 a binary function which returns the desired outcome for a key
289 conflict. These functions will be called as fn(old,new).
292 conflict. These functions will be called as fn(old,new).
290
293
291 An example is probably in order. Suppose you are merging the struct S
294 An example is probably in order. Suppose you are merging the struct S
292 with a dict D and the following conflict policy dict:
295 with a dict D and the following conflict policy dict:
293
296
294 S.merge(D,{fn1:['a','b',4], fn2:'key_c key_d'})
297 S.merge(D,{fn1:['a','b',4], fn2:'key_c key_d'})
295
298
296 If the key 'a' is found in both S and D, the merge method will call:
299 If the key 'a' is found in both S and D, the merge method will call:
297
300
298 S['a'] = fn1(S['a'],D['a'])
301 S['a'] = fn1(S['a'],D['a'])
299
302
300 As a convenience, merge() provides five (the most commonly needed)
303 As a convenience, merge() provides five (the most commonly needed)
301 pre-defined policies: preserve, update, add, add_flip and add_s. The
304 pre-defined policies: preserve, update, add, add_flip and add_s. The
302 easiest explanation is their implementation:
305 easiest explanation is their implementation:
303
306
304 preserve = lambda old,new: old
307 preserve = lambda old,new: old
305 update = lambda old,new: new
308 update = lambda old,new: new
306 add = lambda old,new: old + new
309 add = lambda old,new: old + new
307 add_flip = lambda old,new: new + old # note change of order!
310 add_flip = lambda old,new: new + old # note change of order!
308 add_s = lambda old,new: old + ' ' + new # only works for strings!
311 add_s = lambda old,new: old + ' ' + new # only works for strings!
309
312
310 You can use those four words (as strings) as keys in conflict instead
313 You can use those four words (as strings) as keys in conflict instead
311 of defining them as functions, and the merge method will substitute
314 of defining them as functions, and the merge method will substitute
312 the appropriate functions for you. That is, the call
315 the appropriate functions for you. That is, the call
313
316
314 S.merge(D,{'preserve':'a b c','add':[4,5,'d'],my_function:[6]})
317 S.merge(D,{'preserve':'a b c','add':[4,5,'d'],my_function:[6]})
315
318
316 will automatically substitute the functions preserve and add for the
319 will automatically substitute the functions preserve and add for the
317 names 'preserve' and 'add' before making any function calls.
320 names 'preserve' and 'add' before making any function calls.
318
321
319 For more complicated conflict resolution policies, you still need to
322 For more complicated conflict resolution policies, you still need to
320 construct your own functions. """
323 construct your own functions. """
321
324
322 data_dict = Struct.__make_dict(self,__loc_data__,**kw)
325 data_dict = self.__make_dict(__loc_data__,**kw)
323
326
324 # policies for conflict resolution: two argument functions which return
327 # policies for conflict resolution: two argument functions which return
325 # the value that will go in the new struct
328 # the value that will go in the new struct
326 preserve = lambda old,new: old
329 preserve = lambda old,new: old
@@ -328,10 +331,10 b' class Struct:'
328 add = lambda old,new: old + new
331 add = lambda old,new: old + new
329 add_flip = lambda old,new: new + old # note change of order!
332 add_flip = lambda old,new: new + old # note change of order!
330 add_s = lambda old,new: old + ' ' + new
333 add_s = lambda old,new: old + ' ' + new
331
334
332 # default policy is to keep current keys when there's a conflict
335 # default policy is to keep current keys when there's a conflict
333 conflict_solve = list2dict2(self.keys(),default = preserve)
336 conflict_solve = list2dict2(self.keys(), default = preserve)
334
337
335 # the conflict_solve dictionary is given by the user 'inverted': we
338 # the conflict_solve dictionary is given by the user 'inverted': we
336 # need a name-function mapping, it comes as a function -> names
339 # need a name-function mapping, it comes as a function -> names
337 # dict. Make a local copy (b/c we'll make changes), replace user
340 # dict. Make a local copy (b/c we'll make changes), replace user
@@ -344,7 +347,7 b' class Struct:'
344 if name in inv_conflict_solve_user.keys():
347 if name in inv_conflict_solve_user.keys():
345 inv_conflict_solve_user[func] = inv_conflict_solve_user[name]
348 inv_conflict_solve_user[func] = inv_conflict_solve_user[name]
346 del inv_conflict_solve_user[name]
349 del inv_conflict_solve_user[name]
347 conflict_solve.update(Struct.__dict_invert(self,inv_conflict_solve_user))
350 conflict_solve.update(self.__dict_invert(inv_conflict_solve_user))
348 #print 'merge. conflict_solve: '; pprint(conflict_solve) # dbg
351 #print 'merge. conflict_solve: '; pprint(conflict_solve) # dbg
349 #print '*'*50,'in merger. conflict_solver:'; pprint(conflict_solve)
352 #print '*'*50,'in merger. conflict_solver:'; pprint(conflict_solve)
350 for key in data_dict:
353 for key in data_dict:
@@ -352,64 +355,62 b' class Struct:'
352 self[key] = data_dict[key]
355 self[key] = data_dict[key]
353 else:
356 else:
354 self[key] = conflict_solve[key](self[key],data_dict[key])
357 self[key] = conflict_solve[key](self[key],data_dict[key])
355
358
356 def has_key(self,key):
359 def has_key(self,key):
357 """Like has_key() dictionary method."""
360 """Like has_key() dictionary method."""
358 return self.__dict__.has_key(key)
361 return self._data.has_key(key)
359
362
360 def hasattr(self,key):
363 def hasattr(self,key):
361 """hasattr function available as a method.
364 """hasattr function available as a method.
362
365
363 Implemented like has_key, to make sure that all available keys in the
366 Implemented like has_key, to make sure that all available keys in the
364 internal dictionary of the Struct appear also as attributes (even
367 internal dictionary of the Struct appear also as attributes (even
365 numeric keys)."""
368 numeric keys)."""
366 return self.__dict__.has_key(key)
369 return self._data.has_key(key)
367
370
368 def items(self):
371 def items(self):
369 """Return the items in the Struct's dictionary, in the same format
372 """Return the items in the Struct's dictionary as (key, value)'s."""
370 as a call to {}.items()."""
373 return self._data.items()
371 return self.__dict__.items()
374
372
373 def keys(self):
375 def keys(self):
374 """Return the keys in the Struct's dictionary, in the same format
376 """Return the keys in the Struct's dictionary.."""
375 as a call to {}.keys()."""
377 return self._data.keys()
376 return self.__dict__.keys()
378
377
379 def values(self, keys=None):
378 def values(self,keys=None):
380 """Return the values in the Struct's dictionary.
379 """Return the values in the Struct's dictionary, in the same format
381
380 as a call to {}.values().
381
382 Can be called with an optional argument keys, which must be a list or
382 Can be called with an optional argument keys, which must be a list or
383 tuple of keys. In this case it returns only the values corresponding
383 tuple of keys. In this case it returns only the values corresponding
384 to those keys (allowing a form of 'slicing' for Structs)."""
384 to those keys (allowing a form of 'slicing' for Structs).
385 """
385 if not keys:
386 if not keys:
386 return self.__dict__.values()
387 return self._data.values()
387 else:
388 else:
388 ret=[]
389 result=[]
389 for k in keys:
390 for k in keys:
390 ret.append(self[k])
391 result.append(self[k])
391 return ret
392 return result
392
393
393 def get(self,attr,val=None):
394 def get(self, attr, val=None):
394 """S.get(k[,d]) -> S[k] if k in S, else d. d defaults to None."""
395 """S.get(k[,d]) -> S[k] if k in S, else d. d defaults to None."""
395 try:
396 try:
396 return self[attr]
397 return self[attr]
397 except KeyError:
398 except KeyError:
398 return val
399 return val
399
400
400 def setdefault(self,attr,val=None):
401 def setdefault(self, attr, val=None):
401 """S.setdefault(k[,d]) -> S.get(k,d), also set S[k]=d if k not in S"""
402 """S.setdefault(k[,d]) -> S.get(k,d), also set S[k]=d if k not in S"""
402 if not self.has_key(attr):
403 if not self._data.has_key(attr):
403 self[attr] = val
404 self[attr] = val
404 return self.get(attr,val)
405 return self.get(attr, val)
405
406
406 def allow_new_attr(self, allow = True):
407 def allow_new_attr(self, allow = True):
407 """ Set whether new attributes can be created inside struct
408 """Set whether new attributes can be created in this Struct.
408
409
409 This can be used to catch typos by verifying that the attribute user
410 This can be used to catch typos by verifying that the attribute user
410 tries to change already exists in this Struct.
411 tries to change already exists in this Struct.
411 """
412 """
412 self['__allownew'] = allow
413 object.__setattr__(self, '_allownew', allow)
413
414
414
415
415 # end class Struct
416 # end class Struct
General Comments 0
You need to be logged in to leave comments. Login now