1 """
2 Generic support for objects with full-featured Parameters and
3 messaging.
4
5 $Id: parameterized.py 9417 2008-10-08 20:23:33Z ceball $
6 """
7 __version__='$Revision: 9417 $'
8
9 import sys
10 import copy
11 import re
12
13 from operator import itemgetter,attrgetter
14 from types import FunctionType
15
16
17 SILENT = 0
18 WARNING = 50
19 NORMAL = 100
20 MESSAGE = NORMAL
21 VERBOSE = 200
22 DEBUG = 300
23
24 min_print_level = NORMAL
25
26
27
28 warnings_as_exceptions = False
29
30 object_count = 0
31
32
33 import inspect
35 """
36 Return a list of the class hierarchy above (and including) the given class.
37
38 Same as inspect.getmro(class_)[::-1]
39 """
40 return inspect.getmro(class_)[::-1]
41
44 """
45 Return a list of the class hierarchy below (and including) the given class.
46
47 The list is ordered from least- to most-specific. Can be useful for
48 printing the contents of an entire class hierarchy.
49 """
50 assert isinstance(class_,type)
51 q = [class_]
52 out = []
53 while len(q):
54 x = q.pop(0)
55 out.insert(0,x)
56 for b in x.__subclasses__():
57 if b not in q and b not in out:
58 q.append(b)
59 return out[::-1]
60
64 """
65 Return a list of slot names for slots defined in this class and
66 its superclasses.
67 """
68
69
70
71 all_slots = []
72 parent_param_classes = [class_ for class_ in classlist(class_)[1::]]
73 for class_ in parent_param_classes:
74 if hasattr(class_,'__slots__'):
75 all_slots+=class_.__slots__
76 return all_slots
77
78
79
80
81
82
83
84
85
86
87 from functools import partial
89 """
90 'optional @classmethod'
91
92 A decorator that allows a method to receive either the class
93 object (if called on the class) or the instance object
94 (if called on the instance) as its first argument.
95
96 Code (but not documentation) copied from:
97 http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/523033.
98 """
99
100
103
104
105 - def __get__(self, obj, type_=None):
106 if obj is None:
107 return partial(self.func, type_)
108 else:
109 return partial(self.func, obj)
110
132
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156 -class Parameter(object):
157 """
158 An attribute descriptor for declaring parameters.
159
160 Parameters are a special kind of class attribute. Setting a
161 Parameterized class attribute to be a Parameter instance causes
162 that attribute of the class (and the class's instances) to be
163 treated as a Parameter. This allows special behavior, including
164 dynamically generated parameter values, documentation strings,
165 constant and read-only parameters, and type or range checking at
166 assignment time.
167
168 For example, suppose someone wants to define two new kinds of
169 objects Foo and Bar, such that Bar has a parameter delta, Foo is a
170 subclass of Bar, and Foo has parameters alpha, sigma, and gamma
171 (and delta inherited from Bar). She would begin her class
172 definitions with something like this:
173
174 class Bar(Parameterized):
175 delta = Parameter(default=0.6, doc='The difference between steps.')
176 ...
177
178 class Foo(Bar):
179 alpha = Parameter(default=0.1, doc='The starting value.')
180 sigma = Parameter(default=0.5, doc='The standard deviation.',
181 constant=True)
182 gamma = Parameter(default=1.0, doc='The ending value.')
183 ...
184
185 Class Foo would then have four parameters, with delta defaulting
186 to 0.6.
187
188 Parameters have several advantages over plain attributes:
189
190 1. Parameters can be set automatically when an instance is
191 constructed: The default constructor for Foo (and Bar) will
192 accept arbitrary keyword arguments, each of which can be used
193 to specify the value of a Parameter of Foo (or any of Foo's
194 superclasses). E.g., if a script does this:
195
196 myfoo = Foo(alpha=0.5)
197
198 myfoo.alpha will return 0.5, without the Foo constructor
199 needing special code to set alpha.
200
201 If Foo implements its own constructor, keyword arguments will
202 still be accepted if the constructor accepts a dictionary of
203 keyword arguments (as in ``def __init__(self,**params):``), and
204 then each class calls its superclass (as in
205 ``super(Foo,self).__init__(**params)``) so that the
206 Parameterized constructor will process the keywords.
207
208 2. A Parameterized class need specify only the attributes of a
209 Parameter whose values differ from those declared in
210 superclasses; the other values will be inherited. E.g. if Foo
211 declares
212
213 delta = Parameter(default=0.2)
214
215 the default value of 0.2 will override the 0.6 inherited from
216 Bar, but the doc will be inherited from Bar.
217
218 3. The Parameter descriptor class can be subclassed to provide
219 more complex behavior, allowing special types of parameters
220 that, for example, require their values to be numbers in
221 certain ranges, generate their values dynamically from a random
222 distribution, or read their values from a file or other
223 external source.
224
225 4. The attributes associated with Parameters provide enough
226 information for automatically generating property sheets in
227 graphical user interfaces, allowing Parameterized instances to
228 be edited by users.
229
230 Note that Parameters can only be used when set as class attributes
231 of Parameterized classes. Parameters used as standalone objects,
232 or as class attributes of non-Parameterized classes, will not have
233 the behavior described here.
234 """
235 __metaclass__ = ParameterMetaclass
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291 __slots__ = ['_attrib_name','_internal_name','default','doc',
292 'precedence','instantiate','constant','readonly']
293
294
295
296
297
298
299 - def __init__(self,default=None,doc=None,precedence=None,
300 instantiate=False,constant=False,readonly=False):
301 """
302 Initialize a new Parameter object: store the supplied attributes.
303
304 default: the owning class's value for the attribute
305 represented by this Parameter.
306
307 precedence is a value, usually in the range 0.0 to 1.0, that
308 allows the order of Parameters in a class to be defined (for
309 e.g. in GUI menus). A negative precedence indicates a
310 parameter that should be hidden in e.g. GUI menus.
311
312 default, doc, and precedence default to None. This is to allow
313 inheritance of Parameter slots (attributes) from the owning-class'
314 class hierarchy (see ParameterizedMetaclass).
315 """
316 self._attrib_name = None
317 self._internal_name = None
318 self.precedence = precedence
319 self.default = default
320 self.doc = doc
321 self.constant = constant or readonly
322 self.readonly = readonly
323 self._set_instantiate(instantiate)
324
325
335
336
338 """
339 Return the value for this Parameter.
340
341 If called for a Parameterized class, produce that
342 class's value (i.e. this Parameter object's 'default'
343 attribute).
344
345 If called for a Parameterized instance, produce that
346 instance's value, if one has been set - otherwise produce the
347 class's value (default).
348 """
349
350
351
352 if not obj:
353 result = self.default
354 else:
355 result = obj.__dict__.get(self._internal_name,self.default)
356 return result
357
358
360 """
361 Set the value for this Parameter.
362
363 If called for a Parameterized class, set that class's
364 value (i.e. set this Parameter object's 'default' attribute).
365
366 If called for a Parameterized instance, set the value of
367 this Parameter on that instance (i.e. in the instance's
368 __dict__, under the parameter's internal_name).
369
370
371 If the Parameter's constant attribute is True, only allows
372 the value to be set for a Parameterized class or on
373 uninitialized Parameterized instances.
374
375 If the Parameter's readonly attribute is True, only allows the
376 value to be specified in the Parameter declaration inside the
377 Parameterized source code. A read-only parameter also
378 cannot be set on a Parameterized class.
379
380 Note that until we support some form of read-only
381 object, it is still possible to change the attributes of the
382 object stored in a constant or read-only Parameter (e.g. the
383 left bound of a BoundingBox).
384 """
385
386
387 if self.constant or self.readonly:
388 if self.readonly:
389 raise TypeError("Read-only parameter '%s' cannot be modified"%self._attrib_name)
390 elif not obj:
391 self.default = val
392 elif not obj.initialized:
393 obj.__dict__[self._internal_name] = val
394 else:
395 raise TypeError("Constant parameter '%s' cannot be modified"%self._attrib_name)
396
397 else:
398 if not obj:
399 self.default = val
400 else:
401 obj.__dict__[self._internal_name] = val
402
403
405 raise TypeError("Cannot delete '%s': Parameters deletion not allowed."%self._attrib_name)
406
407
409 self._attrib_name = attrib_name
410 self._internal_name = "_%s_param_value"%attrib_name
411
412
414 """
415 All Parameters have slots, not a dict, so we have to support
416 pickle and deepcopy ourselves.
417 """
418 state = {}
419 for slot in get_all_slots(type(self)):
420 state[slot] = getattr(self,slot)
421
422 return state
423
428