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