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