Package topo :: Package param :: Module parameterized
[hide private]
[frames] | no frames]

Source Code for Module topo.param.parameterized

   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  # JABALERT: Could consider using Python's logging facilities instead. 
  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  # Indicates whether warnings should be raised as errors, stopping 
  27  # processing. 
  28  warnings_as_exceptions = False 
  29   
  30  object_count = 0 
  31   
  32   
  33  import inspect 
34 -def classlist(class_):
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
42 43 -def descendents(class_):
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
61 62 63 -def get_all_slots(class_):
64 """ 65 Return a list of slot names for slots defined in this class and 66 its superclasses. 67 """ 68 # A subclass's __slots__ attribute does not contain slots defined 69 # in its superclass (the superclass' __slots__ end up as 70 # attributes of the subclass). 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 # CEBALERT: decorators hide the docstring when using help(). Consider 82 # the decorator module: would allow doc to be seen for the actual 83 # method rather than seeing 'partial object at ...'. 84 # Not part of standard library: 85 # http://www.phyast.pitt.edu/~micheles/python/documentation.html 86 87 from functools import partial
88 -class bothmethod(object): # pylint: disable-msg=R0903
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 # pylint: disable-msg=R0903 100
101 - def __init__(self, func):
102 self.func = func
103 104 # i.e. this is also a non-data descriptor
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
111 112 -class ParameterMetaclass(type):
113 """ 114 Metaclass allowing control over creation of Parameter classes. 115 """
116 - def __new__(mcs,classname,bases,classdict):
117 # store the class's docstring in __classdoc 118 if '__doc__' in classdict: 119 classdict['__classdoc']=classdict['__doc__'] 120 # when asking for help on Parameter *object*, return the doc 121 # slot 122 classdict['__doc__']=property(attrgetter('doc')) 123 124 # To get the benefit of slots, subclasses must themselves define 125 # __slots__, whether or not they define attributes not present in 126 # the base Parameter class. That's because a subclass will have 127 # a __dict__ unless it also defines __slots__. 128 if '__slots__' not in classdict: 129 classdict['__slots__']=[] 130 131 return type.__new__(mcs,classname,bases,classdict)
132
133 - def __getattribute__(mcs,name):
134 if name=='__doc__': 135 # when asking for help on Parameter *class*, return the 136 # stored class docstring 137 return type.__getattribute__(mcs,'__classdoc') 138 else: 139 return type.__getattribute__(mcs,name)
140
141 142 143 # CEBALERT: we break some aspects of slot handling for Parameter and 144 # Parameterized. The __new__ methods in the metaclasses for those two 145 # classes omit to handle the case where __dict__ is passed in 146 # __slots__ (and they possibly omit other things too). Additionally, 147 # various bits of code in the Parameterized class assumes that all 148 # Parameterized instances have a __dict__, but I'm not sure that's 149 # guaranteed to be true (although it's true at the moment). 150 151 152 # CB: we could maybe reduce the complexity by doing something to allow 153 # a parameter to discover things about itself when created (would also 154 # allow things like checking a Parameter is owned by a 155 # Parameterized). I have some vague ideas about what to do. 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 # Because they implement __get__ and __set__, Parameters are known 238 # as 'descriptors' in Python; see "Implementing Descriptors" and 239 # "Invoking Descriptors" in the 'Customizing attribute access' 240 # section of the Python reference manual: 241 # http://docs.python.org/ref/attribute-access.html 242 # 243 # Overview of Parameters for programmers 244 # ====================================== 245 # 246 # Consider the following code: 247 # 248 # 249 # class A(Parameterized): 250 # p = Parameter(default=1) 251 # 252 # a1 = A() 253 # a2 = A() 254 # 255 # 256 # * a1 and a2 share one Parameter object (A.__dict__['p']). 257 # 258 # * The default (class) value of p is stored in this Parameter 259 # object (A.__dict__['p'].default). 260 # 261 # * If the value of p is set on a1 (e.g. a1.p=2), a1's value of p 262 # is stored in a1 itself (a1.__dict__['_p_param_value']) 263 # 264 # * When a1.p is requested, a1.__dict__['_p_param_value'] is 265 # returned. When a2.p is requested, '_p_param_value' is not 266 # found in a2.__dict__, so A.__dict__['p'].default (i.e. A.p) is 267 # returned instead. 268 # 269 # 270 # Be careful when referring to the 'name' of a Parameter: 271 # 272 # * A Parameterized class has a name for the attribute which is 273 # being represented by the Parameter ('p' in the example above); 274 # in the code, this is called the 'attrib_name'. 275 # 276 # * When a Parameterized instance has its own local value for a 277 # parameter, it is stored as '_X_param_value' (where X is the 278 # attrib_name for the Parameter); in the code, this is called 279 # the internal_name. 280 281 282 # So that the extra features of Parameters do not require a lot of 283 # overhead, Parameters are implemented using __slots__ (see 284 # http://www.python.org/doc/2.4/ref/slots.html). Instead of having 285 # a full Python dictionary associated with each Parameter instance, 286 # Parameter instances have an enumerated list (named __slots__) of 287 # attributes, and reserve just enough space to store these 288 # attributes. Using __slots__ requires special support for 289 # operations to copy and restore Parameters (e.g. for Python 290 # persistent storage pickling); see __getstate__ and __setstate__. 291 __slots__ = ['_attrib_name','_internal_name','default','doc', 292 'precedence','instantiate','constant','readonly'] 293 294 # When created, a Parameter does not know which 295 # Parameterized class owns it. If a Parameter subclass needs 296 # to know the owning class, it can declare an 'objtype' slot 297 # (which will be filled in by ParameterizedMetaclass) 298
299 - def __init__(self,default=None,doc=None,precedence=None, # pylint: disable-msg=R0913 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 # readonly => constant 322 self.readonly = readonly 323 self._set_instantiate(instantiate)
324 325
326 - def _set_instantiate(self,instantiate):
327 """Constant parameters must be instantiated.""" 328 # CB: instantiate doesn't actually matter for read-only 329 # parameters, since they can't be set even on a class. But 330 # this avoids needless instantiation. 331 if self.readonly: 332 self.instantiate = False 333 else: 334 self.instantiate = instantiate or self.constant # pylint: disable-msg=W0201
335 336
337 - def __get__(self,obj,objtype): # pylint: disable-msg=W0613
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 # NB: obj can be None (when __get__ called for a 350 # Parameterized class); objtype is never None 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
359 - def __set__(self,obj,val):
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 # NB: obj can be None (when __set__ called for a 386 # Parameterized class) 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
404 - def __delete__(self,obj):
405 raise TypeError("Cannot delete '%s': Parameters deletion not allowed."%self._attrib_name)
406 407
408 - def _set_names(self,attrib_name):
409 self._attrib_name = attrib_name 410 self._internal_name = "_%s_param_value"%attrib_name 411 412
413 - def __getstate__(self):
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
424 - def __setstate__(self,state):
425 # set values of __slots__ (instead of in non-existent __dict__) 426 for (k,v) in state.items(): 427 setattr(self,k,v)
428
429 430 431 -class ParameterizedMetaclass(type):
432 """ 433 The metaclass of Parameterized (and all its descendents). 434 435 The metaclass overrides type.__setattr__ to allow us to set 436 Parameter values on classes without overwriting the attribute 437 descriptor. That is, for a Parameterized class of type X with a 438 Parameter y, the user can type X.y=3, which sets the default value 439 of Parameter y to be 3, rather than overwriting y with the 440 constant value 3 (and thereby losing all other info about that 441 Parameter, such as the doc string, bounds, etc.). 442 443 The __init__ method is used when defining a Parameterized class, 444 usually when the module where that class is located is imported 445 for the first time. That is, the __init__ in this metaclass 446 initializes the *class* object, while the __init__ method defined 447 in each Parameterized class is called for each new instance of 448 that class. 449 450 Additionally, a class can declare itself abstract by having an 451 attribute __abstract set to True. The 'abstract' attribute can be 452 used to find out if a class is abstract or not. 453 """
454 - def __init__(mcs,name,bases,dict_):
455 """ 456 Initialize the class object (not an instance of the class, but 457 the class itself). 458 459 Initializes all the Parameters by looking up appropriate 460 default values; see __param_inheritance(). 461 """ 462 type.