##// END OF EJS Templates
Full refactor of ipstruct.Struct....
Brian Granger -
Show More
@@ -483,10 +483,10 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)
489 IP_rc.update(rc_override)
488 if rc_override is not None:
489 IP_rc.update(rc_override)
490
490
491 # Store the original cmd line for reference:
491 # Store the original cmd line for reference:
492 IP_rc.opts = opts
492 IP_rc.opts = opts
This diff has been collapsed as it changes many lines, (605 lines changed) Show them Hide them
@@ -1,219 +1,227 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
27
22 functionality.
28 #-----------------------------------------------------------------------------
23
29 # Code
24 Instances can be initialized with a dictionary, a list of key=value pairs
30 #-----------------------------------------------------------------------------
25 or both. If both are present, the dictionary must come first.
31
26
32
27 Because Python classes provide direct assignment to their members, it's
33 class Struct(dict):
28 easy to overwrite normal methods (S.copy = 1 would destroy access to
34 """A dict subclass with attribute style access.
29 S.copy()). For this reason, all builtin method names are protected and
35
30 can't be assigned to. An attempt to do s.copy=1 or s['copy']=1 will raise
36 This dict subclass has a a few extra features:
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
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'
79
80 >>> s[key]
81 10
82
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:
86
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
92
93 But this does:
94 >>> s=Struct()
95 >>> s[4]='hi'
96 >>> s
97 Struct({4: 'hi', '__allownew': True})
98 >>> s[4]
99 'hi'
100 """
101
37
102 # Attributes to which __setitem__ and __setattr__ will block access.
38 * Attribute style access.
103 # Note: much of this will be moot in Python 2.2 and will be done in a much
39 * Protection of class members (like keys, items) when using attribute
104 # cleaner way.
40 style access.
105 __protected = ('copy dict dictcopy get has_attr has_key items keys '
41 * The ability to restrict assignment to only existing keys.
106 'merge popitem setdefault update values '
42 * Intelligent merging.
107 '__make_dict __dict_invert ').split()
43 * Overloaded operators.
44 """
108
45
109 def __init__(self,data=None,**kw):
46 def __init__(self, *args, **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(
126 self.__setitem__(key, value)
150 "can't create unknown attribute %s. Check for typos, or use allow_new_attr" % key)
127 except KeyError, e:
151 self._data[key] = value
128 raise AttributeError(e)
152
153 def __setattr__(self, key, value):
154 self.__setitem__(key, value)
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):
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__()
172
173 def __contains__(self, key):
174 return key in self._data
175
176 def __iadd__(self, other):
158 def __iadd__(self, other):
177 """S += S2 is a shorthand for S.merge(S2)."""
159 """s += s2 is a shorthand for s.merge(s2).
160
161 Examples
162 --------
163
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 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.
207
208 Examples
209 --------
210
211 >>> s1 = Struct(a=10,b=30)
212 >>> s2 = Struct(a=40)
213 >>> s1 -= s2
214 >>> s1
215 {'b': 30}
216 """
195 for k in other.keys():
217 for k in other.keys():
196 if self.has_key(k):
218 if self.has_key(k):
197 del self._data[k]
219 del self[k]
220 return self
198
221
199 def __make_dict(self,__loc_data__,**kw):
200 """Helper function for update and merge. Return a dict from data.
201 """
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
213
214 def __dict_invert(self, data):
222 def __dict_invert(self, data):
215 """Helper function for merge.
223 """Helper function for merge.
216
224
217 Takes a dictionary whose values are lists and returns a dict with
225 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.
226 the elements of each list as keys and the original keys as values.
219 """
227 """
@@ -224,106 +232,140 b' class Struct(object):'
224 for entry in lst:
232 for entry in lst:
225 outdict[entry] = k
233 outdict[entry] = k
226 return outdict
234 return outdict
227
235
228 def clear(self):
229 """Clear all attributes."""
230 self._data.clear()
231
232 def copy(self):
233 """Return a (shallow) copy of a Struct."""
234 return Struct(self._data.copy())
235
236 def dict(self):
236 def dict(self):
237 """Return the Struct's dictionary."""
237 return self
238 return self._data
238
239
239 def copy(self):
240 def dictcopy(self):
240 """Return a copy as a Struct.
241 """Return a (shallow) copy of the Struct's dictionary."""
242 return self._data.copy()
243
244 def popitem(self):
245 """Return (key, value) tuple and remove from Struct.
246
241
247 If key is not present raise KeyError.
242 Examples
248 """
243 --------
249 return self._data.popitem()
250
251 def update(self,__loc_data__=None,**kw):
252 """Update (merge) with data from another Struct or dict.
253
244
254 Parameters
245 >>> s = Struct(a=10,b=30)
255 ----------
246 >>> s2 = s.copy()
256 __loc_data : dict, Struct
247 >>> s2
257 The new data to add to self.
248 {'a': 10, 'b': 30}
258 kw : dict
249 >>> type(s2).__name__
259 Key, value pairs to add to self.
250 'Struct'
251 """
252 return Struct(dict.copy(self))
253
254 def hasattr(self, key):
255 """hasattr function available as a method.
256
257 Implemented like has_key.
258
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
260 """
269 """
261 # The funny name __loc_data__ is to prevent a common variable name
270 return self.has_key(key)
262 # which could be a fieled of a Struct to collide with this
271
263 # parameter. The problem would arise if the function is called with a
272 def allow_new_attr(self, allow = True):
264 # keyword with this same name that a user means to add as a Struct
273 """Set whether new attributes can be created in this Struct.
265 # field.
274
266 newdict = self.__make_dict(__loc_data__, **kw)
275 This can be used to catch typos by verifying that the attribute user
267 for k, v in newdict.iteritems():
276 tries to change already exists in this Struct.
268 self[k] = v
277 """
269
278 object.__setattr__(self, '_allownew', allow)
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
276
286 what to do.
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 ----------
283
294 __loc_data : dict, Struct
284 conflict == { fn1 : [Skey1,Skey2,...], fn2 : [Skey3], etc }
295 The data to merge into self
285
296 __conflict_solve : dict
286 Values must be lists or whitespace separated strings which are
297 The conflict policy dict. The keys are binary functions used to
287 automatically converted to lists of strings by calling string.split().
298 resolve the conflict and the values are lists of strings naming
288
299 the keys the conflict resolution function applies to. Instead of
289 Each key of conflict is a function which defines a policy for
300 a list of strings a space separated string can be used, like
290 resolving conflicts when merging with the input data. Each fn must be
301 'a b c'.
291 a binary function which returns the desired outcome for a key
302 kw : dict
292 conflict. These functions will be called as fn(old,new).
303 Additional key, value pairs to merge in
293
304
294 An example is probably in order. Suppose you are merging the struct S
305 Notes
295 with a dict D and the following conflict policy dict:
306 -----
296
307
297 S.merge(D,{fn1:['a','b',4], fn2:'key_c key_d'})
308 The `__conflict_solve` dict is a dictionary of binary functions which will be used to
298
309 solve key conflicts. Here is an example::
299 If the key 'a' is found in both S and D, the merge method will call:
310
300
311 __conflict_solve = dict(
301 S['a'] = fn1(S['a'],D['a'])
312 func1=['a','b','c'],
302
313 func2=['d','e']
314 )
315
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::
319
320 __conflict_solve = dict(func1='a b c',func2='d e')
321
322 These functions will be called for each key they apply to with the
323 form::
324
325 func1(self['a'], other['a'])
326
327 The return value is used as the final merged value.
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]})
318
319 will automatically substitute the functions preserve and add for the
320 names 'preserve' and 'add' before making any function calls.
321
322 For more complicated conflict resolution policies, you still need to
343 For more complicated conflict resolution policies, you still need to
323 construct your own functions. """
344 construct your own functions.
324
345
325 data_dict = self.__make_dict(__loc_data__,**kw)
346 Examples
326
347 --------
348
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 """
366
367 data_dict = dict(__loc_data__,**kw)
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
329 preserve = lambda old,new: old
371 preserve = lambda old,new: old
@@ -331,10 +373,10 b' class Struct(object):'
331 add = lambda old,new: old + new
373 add = lambda old,new: old + new
332 add_flip = lambda old,new: new + old # note change of order!
374 add_flip = lambda old,new: new + old # note change of order!
333 add_s = lambda old,new: old + ' ' + new
375 add_s = lambda old,new: old + ' ' + new
334
376
335 # default policy is to keep current keys when there's a conflict
377 # default policy is to keep current keys when there's a conflict
336 conflict_solve = list2dict2(self.keys(), default = preserve)
378 conflict_solve = list2dict2(self.keys(), default = preserve)
337
379
338 # the conflict_solve dictionary is given by the user 'inverted': we
380 # the conflict_solve dictionary is given by the user 'inverted': we
339 # need a name-function mapping, it comes as a function -> names
381 # need a name-function mapping, it comes as a function -> names
340 # dict. Make a local copy (b/c we'll make changes), replace user
382 # dict. Make a local copy (b/c we'll make changes), replace user
@@ -355,63 +397,4 b' class Struct(object):'
355 self[key] = data_dict[key]
397 self[key] = data_dict[key]
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
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
400
General Comments 0
You need to be logged in to leave comments. Login now