##// 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 IP_rc.update(opts_def)
484 IP_rc.update(opts_def)
485 if rcfiledata:
485 if rcfiledata:
486 # now we can update
487 IP_rc.update(rcfiledata)
486 IP_rc.update(rcfiledata)
488 IP_rc.update(opts)
487 IP_rc.update(opts)
488 if rc_override is not None:
489 IP_rc.update(rc_override)
489 IP_rc.update(rc_override)
490
490
491 # Store the original cmd line for reference:
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 -*-
1 #!/usr/bin/env python
2 """Mimic C structs with lots of extra functionality.
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 #*****************************************************************************
11 #-----------------------------------------------------------------------------
6 # Copyright (C) 2001-2004 Fernando Perez <fperez@colorado.edu>
12 # Copyright (C) 2008-2009 The IPython Development Team
7 #
13 #
8 # Distributed under the terms of the BSD License. The full license is in
14 # Distributed under the terms of the BSD License. The full license is in
9 # the file COPYING, distributed as part of this software.
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 import pprint
22 import pprint
17
23
18 from IPython.utils.genutils import list2dict2
24 from IPython.utils.genutils import list2dict2
19
25
20 class Struct(object):
26 __all__ = ['Struct']
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
72
27
73 If you want a variable to hold the key value, only dictionary access works:
28 #-----------------------------------------------------------------------------
74 >>> key='hi'
29 # Code
75 >>> s.key
30 #-----------------------------------------------------------------------------
76 Traceback (most recent call last):
77 File "<stdin>", line 1, in ?
78 AttributeError: Struct instance has no attribute 'key'
79
31
80 >>> s[key]
81 10
82
32
83 Another limitation of the s.key syntax (and Struct(key=val)
33 class Struct(dict):
84 initialization): keys can't be numbers. But numeric keys can be used and
34 """A dict subclass with attribute style access.
85 accessed using the dictionary syntax. Again, an example:
86
35
87 This doesn't work (prompt changed to avoid confusing the test system):
36 This dict subclass has a a few extra features:
88 ->> s=Struct(4='hi')
89 Traceback (most recent call last):
90 ...
91 SyntaxError: keyword can't be an expression
92
37
93 But this does:
38 * Attribute style access.
94 >>> s=Struct()
39 * Protection of class members (like keys, items) when using attribute
95 >>> s[4]='hi'
40 style access.
96 >>> s
41 * The ability to restrict assignment to only existing keys.
97 Struct({4: 'hi', '__allownew': True})
42 * Intelligent merging.
98 >>> s[4]
43 * Overloaded operators.
99 'hi'
100 """
44 """
101
45
102 # Attributes to which __setitem__ and __setattr__ will block access.
46 def __init__(self, *args, **kw):
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):
110 """Initialize with a dictionary, another Struct, or data.
47 """Initialize with a dictionary, another Struct, or data.
111
48
112 Parameters
49 Parameters
113 ----------
50 ----------
114 data : dict, Struct
51 args : dict, Struct
115 Initialize with this data.
52 Initialize with one dict or Struct
116 kw : dict
53 kw : dict
117 Initialize with key, value pairs.
54 Initialize with key, value pairs.
118
55
119 Examples
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 object.__setattr__(self, '_allownew', True)
68 object.__setattr__(self, '_allownew', True)
124 object.__setattr__(self, '_data',{})
69 dict.__init__(self, *args, **kw)
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
136
70
137 def __setitem__(self, key, value):
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 if isinstance(key, str):
116 if isinstance(key, str):
140 # I can't simply call hasattr here because it calls getattr, which
117 # I can't simply call hasattr here because it calls getattr, which
141 # calls self.__getattr__, which returns True for keys in
118 # calls self.__getattr__, which returns True for keys in
142 # self._data. But I only want keys in the class and in
119 # self._data. But I only want keys in the class and in
143 # self.__dict__
120 # self.__dict__
144 if key in self.__dict__ or hasattr(Struct, key):
121 if key in self.__dict__ or hasattr(Struct, key):
145 raise KeyError(
122 raise AttributeError(
146 'key %s is a protected key of class Struct.' % key
123 'attr %s is a protected member of class Struct.' % key
147 )
124 )
148 if not self._allownew and key not in self._data:
125 try:
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):
154 self.__setitem__(key, value)
126 self.__setitem__(key, value)
127 except KeyError, e:
128 raise AttributeError(e)
155
129
156 def __getattr__(self, key):
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 try:
151 try:
158 result = self._data[key]
152 result = self[key]
159 except KeyError:
153 except KeyError:
160 raise AttributeError(key)
154 raise AttributeError(key)
161 else:
155 else:
162 return result
156 return result
163
157
164 def __getitem__(self, key):
158 def __iadd__(self, other):
165 return self._data[key]
159 """s += s2 is a shorthand for s.merge(s2).
166
167 def __str__(self):
168 return 'Struct('+ pprint.pformat(self._data)+')'
169
170 def __repr__(self):
171 return self.__str__()
172
160
173 def __contains__(self, key):
161 Examples
174 return key in self._data
162 --------
175
163
176 def __iadd__(self, other):
164 >>> s = Struct(a=10,b=30)
177 """S += S2 is a shorthand for S.merge(S2)."""
165 >>> s2 = Struct(a=20,c=40)
166 >>> s += s2
167 >>> s
168 {'a': 10, 'c': 40, 'b': 30}
169 """
178 self.merge(other)
170 self.merge(other)
179 return self
171 return self
180
172
181 def __add__(self,other):
173 def __add__(self,other):
182 """S + S2 -> New Struct made from S.merge(S2)"""
174 """s + s2 -> New Struct made from s.merge(s2).
183 Sout = self.copy()
175
184 Sout.merge(other)
176 Examples
185 return Sout
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 def __sub__(self,other):
189 def __sub__(self,other):
188 """Out of place remove keys from self that are in other."""
190 """s1 - s2 -> remove keys in s2 from s1.
189 Sout = self.copy()
191
190 Sout -= other
192 Examples
191 return Sout
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 def __isub__(self,other):
205 def __isub__(self,other):
194 """Inplace remove keys from self that are in other."""
206 """Inplace remove keys from self that are in other.
195 for k in other.keys():
207
196 if self.has_key(k):
208 Examples
197 del self._data[k]
209 --------
198
210
199 def __make_dict(self,__loc_data__,**kw):
211 >>> s1 = Struct(a=10,b=30)
200 """Helper function for update and merge. Return a dict from data.
212 >>> s2 = Struct(a=40)
213 >>> s1 -= s2
214 >>> s1
215 {'b': 30}
201 """
216 """
202 if __loc_data__ == None:
217 for k in other.keys():
203 data = {}
218 if self.has_key(k):
204 elif isinstance(__loc_data__, dict):
219 del self[k]
205 data = __loc_data__
220 return self
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
213
221
214 def __dict_invert(self, data):
222 def __dict_invert(self, data):
215 """Helper function for merge.
223 """Helper function for merge.
@@ -225,104 +233,138 b' class Struct(object):'
225 outdict[entry] = k
233 outdict[entry] = k
226 return outdict
234 return outdict
227
235
228 def clear(self):
236 def dict(self):
229 """Clear all attributes."""
237 return self
230 self._data.clear()
231
238
232 def copy(self):
239 def copy(self):
233 """Return a (shallow) copy of a Struct."""
240 """Return a copy as a Struct.
234 return Struct(self._data.copy())
235
241
236 def dict(self):
242 Examples
237 """Return the Struct's dictionary."""
243 --------
238 return self._data
239
244
240 def dictcopy(self):
245 >>> s = Struct(a=10,b=30)
241 """Return a (shallow) copy of the Struct's dictionary."""
246 >>> s2 = s.copy()
242 return self._data.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):
257 Implemented like has_key.
245 """Return (key, value) tuple and remove from Struct.
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):
272 def allow_new_attr(self, allow = True):
252 """Update (merge) with data from another Struct or dict.
273 """Set whether new attributes can be created in this Struct.
253
274
254 Parameters
275 This can be used to catch typos by verifying that the attribute user
255 ----------
276 tries to change already exists in this Struct.
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 """
277 """
261 # The funny name __loc_data__ is to prevent a common variable name
278 object.__setattr__(self, '_allownew', allow)
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
269
279
270 def merge(self, __loc_data__=None, __conflict_solve=None, **kw):
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
283 This is similar to :meth:`update`, but much more flexible. First, a
274 made from data+key=value pairs. When merging this dict with the Struct
284 dict is made from data+key=value pairs. When merging this dict with
275 S, the optional dictionary 'conflict' is used to decide what to do.
285 the Struct S, the optional dictionary 'conflict' is used to decide
286 what to do.
276
287
277 If conflict is not given, the default behavior is to preserve any keys
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 behavior).
290 behavior).
280
291
281 conflict is a dictionary of binary functions which will be used to
292 Parameters
282 solve key conflicts. It must have the following structure:
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
308 The `__conflict_solve` dict is a dictionary of binary functions which will be used to
287 automatically converted to lists of strings by calling string.split().
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
316 In this case, the function :func:`func1` will be used to resolve
290 resolving conflicts when merging with the input data. Each fn must be
317 keys 'a', 'b' and 'c' and the function :func:`func2` will be used for
291 a binary function which returns the desired outcome for a key
318 keys 'd' and 'e'. This could also be written as::
292 conflict. These functions will be called as fn(old,new).
293
319
294 An example is probably in order. Suppose you are merging the struct S
320 __conflict_solve = dict(func1='a b c',func2='d e')
295 with a dict D and the following conflict policy dict:
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 As a convenience, merge() provides five (the most commonly needed)
329 As a convenience, merge() provides five (the most commonly needed)
304 pre-defined policies: preserve, update, add, add_flip and add_s. The
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 preserve = lambda old,new: old
333 preserve = lambda old,new: old
308 update = lambda old,new: new
334 update = lambda old,new: new
309 add = lambda old,new: old + new
335 add = lambda old,new: old + new
310 add_flip = lambda old,new: new + old # note change of order!
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 of defining them as functions, and the merge method will substitute
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
346 Examples
320 names 'preserve' and 'add' before making any function calls.
347 --------
321
348
322 For more complicated conflict resolution policies, you still need to
349 This show the default policy:
323 construct your own functions. """
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 # policies for conflict resolution: two argument functions which return
369 # policies for conflict resolution: two argument functions which return
328 # the value that will go in the new struct
370 # the value that will go in the new struct
@@ -356,62 +398,3 b' class Struct(object):'
356 else:
398 else:
357 self[key] = conflict_solve[key](self[key],data_dict[key])
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