##// END OF EJS Templates
Struct no pprints the dict inside it
vivainio -
Show More
@@ -1,393 +1,394 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Mimic C structs with lots of extra functionality.
3 3
4 $Id: ipstruct.py 1949 2006-11-28 19:12:15Z vivainio $"""
4 $Id: ipstruct.py 1950 2006-11-28 19:15:35Z vivainio $"""
5 5
6 6 #*****************************************************************************
7 7 # Copyright (C) 2001-2004 Fernando Perez <fperez@colorado.edu>
8 8 #
9 9 # Distributed under the terms of the BSD License. The full license is in
10 10 # the file COPYING, distributed as part of this software.
11 11 #*****************************************************************************
12 12
13 13 from IPython import Release
14 14 __author__ = '%s <%s>' % Release.authors['Fernando']
15 15 __license__ = Release.license
16 16
17 17 __all__ = ['Struct']
18 18
19 19 import types
20 import pprint
20 21
21 22 from IPython.genutils import list2dict2
22 23
23 24 class Struct:
24 25 """Class to mimic C structs but also provide convenient dictionary-like
25 26 functionality.
26 27
27 28 Instances can be initialized with a dictionary, a list of key=value pairs
28 29 or both. If both are present, the dictionary must come first.
29 30
30 31 Because Python classes provide direct assignment to their members, it's
31 32 easy to overwrite normal methods (S.copy = 1 would destroy access to
32 33 S.copy()). For this reason, all builtin method names are protected and
33 34 can't be assigned to. An attempt to do s.copy=1 or s['copy']=1 will raise
34 35 a KeyError exception. If you really want to, you can bypass this
35 36 protection by directly assigning to __dict__: s.__dict__['copy']=1 will
36 37 still work. Doing this will break functionality, though. As in most of
37 38 Python, namespace protection is weakly enforced, so feel free to shoot
38 39 yourself if you really want to.
39 40
40 41 Note that this class uses more memory and is *much* slower than a regular
41 42 dictionary, so be careful in situations where memory or performance are
42 43 critical. But for day to day use it should behave fine. It is particularly
43 44 convenient for storing configuration data in programs.
44 45
45 46 +,+=,- and -= are implemented. +/+= do merges (non-destructive updates),
46 47 -/-= remove keys from the original. See the method descripitions.
47 48
48 49 This class allows a quick access syntax: both s.key and s['key'] are
49 50 valid. This syntax has a limitation: each 'key' has to be explicitly
50 51 accessed by its original name. The normal s.key syntax doesn't provide
51 52 access to the keys via variables whose values evaluate to the desired
52 53 keys. An example should clarify this:
53 54
54 55 Define a dictionary and initialize both with dict and k=v pairs:
55 56 >>> d={'a':1,'b':2}
56 57 >>> s=Struct(d,hi=10,ho=20)
57 58 The return of __repr__ can be used to create a new instance:
58 59 >>> s
59 60 Struct({'ho': 20, 'b': 2, 'hi': 10, 'a': 1})
60 61 __str__ (called by print) shows it's not quite a regular dictionary:
61 62 >>> print s
62 63 Struct {a: 1, b: 2, hi: 10, ho: 20}
63 64 Access by explicitly named key with dot notation:
64 65 >>> s.a
65 66 1
66 67 Or like a dictionary:
67 68 >>> s['a']
68 69 1
69 70 If you want a variable to hold the key value, only dictionary access works:
70 71 >>> key='hi'
71 72 >>> s.key
72 73 Traceback (most recent call last):
73 74 File "<stdin>", line 1, in ?
74 75 AttributeError: Struct instance has no attribute 'key'
75 76 >>> s[key]
76 77 10
77 78
78 79 Another limitation of the s.key syntax (and Struct(key=val)
79 80 initialization): keys can't be numbers. But numeric keys can be used and
80 81 accessed using the dictionary syntax. Again, an example:
81 82
82 83 This doesn't work:
83 84 >>> s=Struct(4='hi')
84 85 SyntaxError: keyword can't be an expression
85 86 But this does:
86 87 >>> s=Struct()
87 88 >>> s[4]='hi'
88 89 >>> s
89 90 Struct({4: 'hi'})
90 91 >>> s[4]
91 92 'hi'
92 93 """
93 94
94 95 # Attributes to which __setitem__ and __setattr__ will block access.
95 96 # Note: much of this will be moot in Python 2.2 and will be done in a much
96 97 # cleaner way.
97 98 __protected = ('copy dict dictcopy get has_attr has_key items keys '
98 99 'merge popitem setdefault update values '
99 100 '__make_dict __dict_invert ').split()
100 101
101 102 def __init__(self,dict=None,**kw):
102 103 """Initialize with a dictionary, another Struct, or by giving
103 104 explicitly the list of attributes.
104 105
105 106 Both can be used, but the dictionary must come first:
106 107 Struct(dict), Struct(k1=v1,k2=v2) or Struct(dict,k1=v1,k2=v2).
107 108 """
108 109 self.__dict__['__allownew'] = True
109 110 if dict is None:
110 111 dict = {}
111 112 if isinstance(dict,Struct):
112 113 dict = dict.dict()
113 114 elif dict and type(dict) is not types.DictType:
114 115 raise TypeError,\
115 116 'Initialize with a dictionary or key=val pairs.'
116 117 dict.update(kw)
117 118 # do the updating by hand to guarantee that we go through the
118 119 # safety-checked __setitem__
119 120 for k,v in dict.items():
120 121 self[k] = v
121 122
122 123
123 124 def __setitem__(self,key,value):
124 125 """Used when struct[key] = val calls are made."""
125 126 if key in Struct.__protected:
126 127 raise KeyError,'Key '+`key`+' is a protected key of class Struct.'
127 128 if not self['__allownew'] and key not in self.__dict__:
128 129 raise KeyError(
129 130 "Can't create unknown attribute %s - Check for typos, or use allow_new_attr to create new attributes!" %
130 131 key)
131 132
132 133 self.__dict__[key] = value
133 134
134 135 def __setattr__(self, key, value):
135 136 """Used when struct.key = val calls are made."""
136 137 self.__setitem__(key,value)
137 138
138 139 def __str__(self):
139 140 """Gets called by print."""
140 141
141 return 'Struct('+str(self.__dict__)+')'
142 return 'Struct('+ pprint.pformat(self.__dict__)+')'
142 143
143 144 def __repr__(self):
144 145 """Gets called by repr.
145 146
146 147 A Struct can be recreated with S_new=eval(repr(S_old))."""
147 return 'Struct('+str(self.__dict__)+')'
148 return self.__str__()
148 149
149 150 def __getitem__(self,key):
150 151 """Allows struct[key] access."""
151 152 return self.__dict__[key]
152 153
153 154 def __contains__(self,key):
154 155 """Allows use of the 'in' operator."""
155 156 return self.__dict__.has_key(key)
156 157
157 158 def __iadd__(self,other):
158 159 """S += S2 is a shorthand for S.merge(S2)."""
159 160 self.merge(other)
160 161 return self
161 162
162 163 def __add__(self,other):
163 164 """S + S2 -> New Struct made form S and S.merge(S2)"""
164 165 Sout = self.copy()
165 166 Sout.merge(other)
166 167 return Sout
167 168
168 169 def __sub__(self,other):
169 170 """Return S1-S2, where all keys in S2 have been deleted (if present)
170 171 from S1."""
171 172 Sout = self.copy()
172 173 Sout -= other
173 174 return Sout
174 175
175 176 def __isub__(self,other):
176 177 """Do in place S = S - S2, meaning all keys in S2 have been deleted
177 178 (if present) from S1."""
178 179
179 180 for k in other.keys():
180 181 if self.has_key(k):
181 182 del self.__dict__[k]
182 183
183 184 def __make_dict(self,__loc_data__,**kw):
184 185 "Helper function for update and merge. Return a dict from data."
185 186
186 187 if __loc_data__ == None:
187 188 dict = {}
188 189 elif type(__loc_data__) is types.DictType:
189 190 dict = __loc_data__
190 191 elif isinstance(__loc_data__,Struct):
191 192 dict = __loc_data__.__dict__
192 193 else:
193 194 raise TypeError, 'Update with a dict, a Struct or key=val pairs.'
194 195 if kw:
195 196 dict.update(kw)
196 197 return dict
197 198
198 199 def __dict_invert(self,dict):
199 200 """Helper function for merge. Takes a dictionary whose values are
200 201 lists and returns a dict. with the elements of each list as keys and
201 202 the original keys as values."""
202 203
203 204 outdict = {}
204 205 for k,lst in dict.items():
205 206 if type(lst) is types.StringType:
206 207 lst = lst.split()
207 208 for entry in lst:
208 209 outdict[entry] = k
209 210 return outdict
210 211
211 212 def clear(self):
212 213 """Clear all attributes."""
213 214 self.__dict__.clear()
214 215
215 216 def copy(self):
216 217 """Return a (shallow) copy of a Struct."""
217 218 return Struct(self.__dict__.copy())
218 219
219 220 def dict(self):
220 221 """Return the Struct's dictionary."""
221 222 return self.__dict__
222 223
223 224 def dictcopy(self):
224 225 """Return a (shallow) copy of the Struct's dictionary."""
225 226 return self.__dict__.copy()
226 227
227 228 def popitem(self):
228 229 """S.popitem() -> (k, v), remove and return some (key, value) pair as
229 230 a 2-tuple; but raise KeyError if S is empty."""
230 231 return self.__dict__.popitem()
231 232
232 233 def update(self,__loc_data__=None,**kw):
233 234 """Update (merge) with data from another Struct or from a dictionary.
234 235 Optionally, one or more key=value pairs can be given at the end for
235 236 direct update."""
236 237
237 238 # The funny name __loc_data__ is to prevent a common variable name which
238 239 # could be a fieled of a Struct to collide with this parameter. The problem
239 240 # would arise if the function is called with a keyword with this same name
240 241 # that a user means to add as a Struct field.
241 242 newdict = Struct.__make_dict(self,__loc_data__,**kw)
242 243 for k,v in newdict.items():
243 244 self[k] = v
244 245
245 246 def merge(self,__loc_data__=None,__conflict_solve=None,**kw):
246 247 """S.merge(data,conflict,k=v1,k=v2,...) -> merge data and k=v into S.
247 248
248 249 This is similar to update(), but much more flexible. First, a dict is
249 250 made from data+key=value pairs. When merging this dict with the Struct
250 251 S, the optional dictionary 'conflict' is used to decide what to do.
251 252
252 253 If conflict is not given, the default behavior is to preserve any keys
253 254 with their current value (the opposite of the update method's
254 255 behavior).
255 256
256 257 conflict is a dictionary of binary functions which will be used to
257 258 solve key conflicts. It must have the following structure:
258 259
259 260 conflict == { fn1 : [Skey1,Skey2,...], fn2 : [Skey3], etc }
260 261
261 262 Values must be lists or whitespace separated strings which are
262 263 automatically converted to lists of strings by calling string.split().
263 264
264 265 Each key of conflict is a function which defines a policy for
265 266 resolving conflicts when merging with the input data. Each fn must be
266 267 a binary function which returns the desired outcome for a key
267 268 conflict. These functions will be called as fn(old,new).
268 269
269 270 An example is probably in order. Suppose you are merging the struct S
270 271 with a dict D and the following conflict policy dict:
271 272
272 273 S.merge(D,{fn1:['a','b',4], fn2:'key_c key_d'})
273 274
274 275 If the key 'a' is found in both S and D, the merge method will call:
275 276
276 277 S['a'] = fn1(S['a'],D['a'])
277 278
278 279 As a convenience, merge() provides five (the most commonly needed)
279 280 pre-defined policies: preserve, update, add, add_flip and add_s. The
280 281 easiest explanation is their implementation:
281 282
282 283 preserve = lambda old,new: old
283 284 update = lambda old,new: new
284 285 add = lambda old,new: old + new
285 286 add_flip = lambda old,new: new + old # note change of order!
286 287 add_s = lambda old,new: old + ' ' + new # only works for strings!
287 288
288 289 You can use those four words (as strings) as keys in conflict instead
289 290 of defining them as functions, and the merge method will substitute
290 291 the appropriate functions for you. That is, the call
291 292
292 293 S.merge(D,{'preserve':'a b c','add':[4,5,'d'],my_function:[6]})
293 294
294 295 will automatically substitute the functions preserve and add for the
295 296 names 'preserve' and 'add' before making any function calls.
296 297
297 298 For more complicated conflict resolution policies, you still need to
298 299 construct your own functions. """
299 300
300 301 data_dict = Struct.__make_dict(self,__loc_data__,**kw)
301 302
302 303 # policies for conflict resolution: two argument functions which return
303 304 # the value that will go in the new struct
304 305 preserve = lambda old,new: old
305 306 update = lambda old,new: new
306 307 add = lambda old,new: old + new
307 308 add_flip = lambda old,new: new + old # note change of order!
308 309 add_s = lambda old,new: old + ' ' + new
309 310
310 311 # default policy is to keep current keys when there's a conflict
311 312 conflict_solve = list2dict2(self.keys(),default = preserve)
312 313
313 314 # the conflict_solve dictionary is given by the user 'inverted': we
314 315 # need a name-function mapping, it comes as a function -> names
315 316 # dict. Make a local copy (b/c we'll make changes), replace user
316 317 # strings for the three builtin policies and invert it.
317 318 if __conflict_solve:
318 319 inv_conflict_solve_user = __conflict_solve.copy()
319 320 for name, func in [('preserve',preserve), ('update',update),
320 321 ('add',add), ('add_flip',add_flip), ('add_s',add_s)]:
321 322 if name in inv_conflict_solve_user.keys():
322 323 inv_conflict_solve_user[func] = inv_conflict_solve_user[name]
323 324 del inv_conflict_solve_user[name]
324 325 conflict_solve.update(Struct.__dict_invert(self,inv_conflict_solve_user))
325 326 #print 'merge. conflict_solve: '; pprint(conflict_solve) # dbg
326 327 #print '*'*50,'in merger. conflict_solver:'; pprint(conflict_solve)
327 328 for key in data_dict:
328 329 if key not in self:
329 330 self[key] = data_dict[key]
330 331 else:
331 332 self[key] = conflict_solve[key](self[key],data_dict[key])
332 333
333 334 def has_key(self,key):
334 335 """Like has_key() dictionary method."""
335 336 return self.__dict__.has_key(key)
336 337
337 338 def hasattr(self,key):
338 339 """hasattr function available as a method.
339 340
340 341 Implemented like has_key, to make sure that all available keys in the
341 342 internal dictionary of the Struct appear also as attributes (even
342 343 numeric keys)."""
343 344 return self.__dict__.has_key(key)
344 345
345 346 def items(self):
346 347 """Return the items in the Struct's dictionary, in the same format
347 348 as a call to {}.items()."""
348 349 return self.__dict__.items()
349 350
350 351 def keys(self):
351 352 """Return the keys in the Struct's dictionary, in the same format
352 353 as a call to {}.keys()."""
353 354 return self.__dict__.keys()
354 355
355 356 def values(self,keys=None):
356 357 """Return the values in the Struct's dictionary, in the same format
357 358 as a call to {}.values().
358 359
359 360 Can be called with an optional argument keys, which must be a list or
360 361 tuple of keys. In this case it returns only the values corresponding
361 362 to those keys (allowing a form of 'slicing' for Structs)."""
362 363 if not keys:
363 364 return self.__dict__.values()
364 365 else:
365 366 ret=[]
366 367 for k in keys:
367 368 ret.append(self[k])
368 369 return ret
369 370
370 371 def get(self,attr,val=None):
371 372 """S.get(k[,d]) -> S[k] if S.has_key(k), else d. d defaults to None."""
372 373 try:
373 374 return self[attr]
374 375 except KeyError:
375 376 return val
376 377
377 378 def setdefault(self,attr,val=None):
378 379 """S.setdefault(k[,d]) -> S.get(k,d), also set S[k]=d if not S.has_key(k)"""
379 380 if not self.has_key(attr):
380 381 self[attr] = val
381 382 return self.get(attr,val)
382 383
383 384 def allow_new_attr(self, allow = True):
384 385 """ Set whether new attributes can be created inside struct
385 386
386 387 This can be used to catch typos by verifying that the attribute user tries to
387 388 change already exists in this Struct.
388 389 """
389 390 self['__allownew'] = allow
390 391
391 392
392 393 # end class Struct
393 394
General Comments 0
You need to be logged in to leave comments. Login now