Package mvpa :: Package misc :: Module state
[hide private]
[frames] | no frames]

Source Code for Module mvpa.misc.state

   1  #Emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- 
   2  #ex: set sts=4 ts=4 sw=4 et: 
   3  ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## 
   4  # 
   5  #   See COPYING file distributed along with the PyMVPA package for the 
   6  #   copyright and license terms. 
   7  # 
   8  ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## 
   9  """Classes to control and store state information. 
  10   
  11  It was devised to provide conditional storage  
  12  """ 
  13   
  14  __docformat__ = 'restructuredtext' 
  15   
  16  import operator, copy 
  17  from sets import Set 
  18  from textwrap import TextWrapper 
  19   
  20  from mvpa.misc.vproperty import VProperty 
  21  from mvpa.misc.exceptions import UnknownStateError 
  22  from mvpa.misc import warning 
  23  from mvpa.base.dochelpers import enhancedClassDocString, enhancedDocString 
  24   
  25  if __debug__: 
  26      from mvpa.misc import debug 
27 28 -class CollectableAttribute(object):
29 """Base class for any custom behaving attribute intended to become part of a collection 30 31 Derived classes will have specific semantics: 32 * StateVariable: conditional storage 33 * Parameter: attribute with validity ranges. 34 - ClassifierParameter: specialization to become a part of 35 Classifier's params collection 36 - KernelParameter: --//-- to become a part of Kernel Classifier's 37 kernel_params collection 38 39 Those CollectableAttributes are to be groupped into corresponding groups for each class 40 by statecollector metaclass 41 """ 42
43 - def __init__(self, name=None, doc=None):
44 self.__doc__ = doc 45 self.__name = name 46 self._value = None 47 self.reset() 48 if __debug__: 49 debug("COL", 50 "Initialized new collectable %s " % name + `self`)
51 52 # Instead of going for VProperty lets make use of virtual method
53 - def _getVirtual(self): return self._get()
54 - def _setVirtual(self, value): return self._set(value)
55
56 - def _get(self):
57 return self._value
58
59 - def _set(self, val):
60 if __debug__: 61 # Since this call is quite often, don't convert 62 # values to strings here, rely on passing them 63 # withing msgargs 64 debug("COL", 65 "Setting %(self)s to %(val)s ", 66 msgargs={'self':self, 'val':val}) 67 self._value = val 68 self._isset = True
69 70 @property
71 - def isSet(self):
72 return self._isset
73
74 - def reset(self):
75 """Simply reset the flag""" 76 if __debug__: 77 debug("COL", "Reset %s to being non-modified" % self.name) 78 self._isset = False
79 80 # TODO XXX unify all bloody __str__
81 - def __str__(self):
82 res = "%s" % (self.name) 83 if self.isSet: 84 res += '*' # so we have the value already 85 return res
86
87 - def _getName(self):
88 return self.__name
89
90 - def _setName(self, name):
91 if name is not None: 92 if isinstance(name, basestring): 93 if name[0] == '_': 94 raise ValueError, \ 95 "Collectable attribute name must not start with _. Got %s" % name 96 else: 97 raise ValueError, \ 98 "Collectable attribute name must be a string. Got %s" % `name` 99 self.__name = name
100 101 # XXX should become vproperty? 102 value = property(_getVirtual, _setVirtual) 103 name = property(_getName, _setName)
104
105 106 -class StateVariable(CollectableAttribute):
107 """Simple container intended to conditionally store the value 108 109 Statefull class provides easy interfact to access the variable 110 (simply through an attribute), or modifying internal state 111 (enable/disable) via .states attribute of type StateCollection. 112 """ 113
114 - def __init__(self, name=None, enabled=True, doc="State variable"):
115 CollectableAttribute.__init__(self, name, doc) 116 self._isenabled = enabled 117 self._defaultenabled = enabled 118 if __debug__: 119 debug("STV", 120 "Initialized new state variable %s " % name + `self`)
121 122
123 - def _get(self):
124 if not self.isSet: 125 raise UnknownStateError("Unknown yet value of %s" % (self.name)) 126 return CollectableAttribute._get(self)
127
128 - def _set(self, val):
129 if __debug__: 130 # Since this call is quite often, don't convert 131 # values to strings here, rely on passing them 132 # withing msgargs 133 debug("COL", 134 "Setting %(self)s to %(val)s ", 135 msgargs={'self':self, 'val':val}) 136 if self.isEnabled: 137 # XXX may be should have left simple assignment 138 # self._value = val 139 CollectableAttribute._set(self, val)
140
141 - def reset(self):
142 """Simply detach the value, and reset the flag""" 143 CollectableAttribute.reset(self) 144 self._value = None
145 146 @property
147 - def isEnabled(self):
148 return self._isenabled
149
150 - def enable(self, value=False):
151 if self._isenabled == value: 152 # Do nothing since it is already in proper state 153 return 154 if __debug__: 155 debug("STV", "%s %s" % 156 ({True: 'Enabling', False: 'Disabling'}[value], str(self))) 157 self._isenabled = value
158 159
160 - def __str__(self):
161 res = CollectableAttribute.__str__(self) 162 if self.isEnabled: 163 res += '+' # it is enabled but no value is assigned yet 164 return res
165
166 167 -class Collection(object):
168 """Container of some CollectableAttributes. 169 170 :Groups: 171 - `Public Access Functions`: `isKnown` 172 - `Access Implementors`: `_getListing`, `_getNames` 173 - `Mutators`: `__init__` 174 - `R/O Properties`: `listing`, `names`, `items` 175 176 XXX Seems to be not used and duplicating functionality: `_getListing` (thus `listing` property) 177 """ 178
179 - def __init__(self, items=None, owner=None):
180 """Initialize the Collection 181 182 :Parameters: 183 items : dict of CollectableAttribute's 184 items to initialize with 185 enable_states : list 186 list of states to enable. If it contains 'all' (in any casing), 187 then all states (besides the ones in disable_states) will be enabled 188 disable_states : list 189 list of states to disable 190 """ 191 192 self.__owner = owner 193 194 if items == None: 195 items = {} 196 self._items = items 197 """Dictionary to contain registered states as keys and 198 values signal either they are enabled 199 """
200 201
202 - def __str__(self):
203 num = len(self._items) 204 res = "{" 205 for i in xrange(min(num, 4)): 206 if i>0: res += " " 207 res += "%s" % str(self._items.values()[i]) 208 if len(self._items) > 4: 209 res += "..." 210 res += "}" 211 if __debug__: 212 if "ST" in debug.active: 213 res += " owner:%s" % `self.owner` 214 return res
215 216 # 217 # XXX TODO: figure out if there is a way to define proper 218 # __copy__'s for a hierarchy of classes. Probably we had 219 # to define __getinitargs__, etc... read more... 220 # 221 #def __copy__(self): 222 # TODO Remove or refactor? 223 # def _copy_states_(self, fromstate, deep=False): 224 # """Copy known here states from `fromstate` object into current object 225 # 226 # Crafted to overcome a problem mentioned above in the comment 227 # and is to be called from __copy__ of derived classes 228 # 229 # Probably sooner than later will get proper __getstate__, 230 # __setstate__ 231 # """ 232 # # Bad check... doesn't generalize well... 233 # # if not issubclass(fromstate.__class__, self.__class__): 234 # # raise ValueError, \ 235 # # "Class %s is not subclass of %s, " % \ 236 # # (fromstate.__class__, self.__class__) + \ 237 # # "thus not eligible for _copy_states_" 238 # # TODO: FOR NOW NO TEST! But this beast needs to be fixed... 239 # operation = { True: copy.deepcopy, 240 # False: copy.copy }[deep] 241 # 242 # if isinstance(fromstate, Stateful): 243 # fromstate = fromstate.states 244 # 245 # self.enabled = fromstate.enabled 246 # for name in self.names: 247 # if fromstate.isKnown(name): 248 # self._items[name] = operation(fromstate._items[name]) 249
250 - def isKnown(self, index):
251 """Returns `True` if state `index` is known at all""" 252 return self._items.has_key(index)
253
254 - def isSet(self, index):
255 """Returns `True` if state `index` has value set""" 256 self._checkIndex(index) 257 return self._items[index].isSet
258
259 - def _checkIndex(self, index):
260 """Verify that given `index` is a known/registered state. 261 262 :Raise `KeyError`: if given `index` is not known 263 """ 264 if not self.isKnown(index): 265 raise KeyError, \ 266 "%s of %s has no key '%s' registered" \ 267 % (self.__class__.__name__, 268 self.__owner.__class__.__name__, 269 index)
270
271 - def add(self, item):
272 """Add a new CollectableAttribute to the collection 273 274 :Parameters: 275 item : CollectableAttribute 276 or of derived class. Must have 'name' assigned 277 278 TODO: we should make it stricter to don't add smth of 279 wrong type into Collection since it might lead to problems 280 281 Also we might convert to __setitem__ 282 """ 283 if not isinstance(item, CollectableAttribute): 284 raise ValueError, \ 285 "Collection can add only instances of " + \ 286 "CollectableAttribute-derived classes. Got %s" % `item` 287 if item.name is None: 288 raise ValueError, \ 289 "CollectableAttribute to be added %s must have 'name' set" % \ 290 item 291 self._items[item.name] = item 292 293 if not self.owner is None: 294 self._updateOwner(item.name)
295
296 - def remove(self, index):
297 """Remove item from the collection 298 """ 299 self._checkIndex(index) 300 self._updateOwner(index, register=False) 301 discard = self._items.pop(index)
302 303
304 - def __getattribute__(self, index):
305 """ 306 """ 307 #return all private and protected ones first since we will not have 308 # collectable's with _ (we should not have!) 309 if index[0] == '_': 310 return object.__getattribute__(self, index) 311 if self._items.has_key(index): 312 return self._items[index].value 313 return object.__getattribute__(self, index)
314 315
316 - def __setattr__(self, index, value):
317 if index[0] == '_': 318 return object.__setattr__(self, index, value) 319 if self._items.has_key(index): 320 self._items[index].value = value 321 return 322 object.__setattr__(self, index, value)
323 324
325 - def __getitem__(self, index):
326 if self._items.has_key(index): 327 self._checkIndex(index) 328 return self._items[index] 329 else: 330 raise AttributeError("State collection %s has no %s attribute" % (self, index))
331 332 # Probably not needed -- enable if need arises 333 # 334 #def __setattr__(self, index, value): 335 # if self._items.has_key(index): 336 # self._updateOwner(index, register=False) 337 # self._items[index] = value 338 # self._updateOwner(index, register=True) 339 # 340 # object.__setattr__(self, index, value) 341 342
343 - def getvalue(self, index):
344 """Returns the value by index""" 345 self._checkIndex(index) 346 return self._items[index].value
347
348 - def setvalue(self, index, value):
349 """Sets the value by index""" 350 self._checkIndex(index) 351 self._items[index].value = value
352 353
354 - def _action(self, index, func, missingok=False, **kwargs):
355 """Run specific func either on a single item or on all of them 356 357 :Parameters: 358 index : basestr 359 Name of the state variable 360 func 361 Function (not bound) to call given an item, and **kwargs 362 missingok : bool 363 If True - do not complain about wrong index 364 """ 365 if isinstance(index, basestring): 366 if index.upper() == 'ALL': 367 for index_ in self._items: 368 self._action(index_, func, missingok=missingok, **kwargs) 369 else: 370 try: 371 self._checkIndex(index) 372 func(self._items[index], **kwargs) 373 except: 374 if missingok: 375 return 376 raise 377 elif operator.isSequenceType(index): 378 for item in index: 379 self._action(item, func, missingok=missingok, **kwargs) 380 else: 381 raise ValueError, \ 382 "Don't know how to handle variable given by %s" % index
383
384 - def reset(self, index=None):
385 """Reset the state variable defined by `index`""" 386 387 if not index is None: 388 indexes = [ index ] 389 else: 390 indexes = self.names 391 392 if len(self.items): 393 for index in indexes: 394 # XXX Check if that works as desired 395 self._action(index, self._items.values()[0].__class__.reset, 396 missingok=False)
397 398
399 - def _getListing(self):
400 """Return a list of registered states along with the documentation""" 401 402 # lets assure consistent litsting order 403 items = self._items.items() 404 items.sort() 405 return [ "%s: %s" % (str(x[1]), x[1].__doc__) for x in items ]
406 407
408 - def _getNames(self):
409 """Return ids for all registered state variables""" 410 return self._items.keys()
411 412
413 - def _getOwner(self):
414 return self.__owner
415
416 - def _setOwner(self, owner):
417 if not isinstance(owner, Stateful): 418 raise ValueError, \ 419 "Owner of the StateCollection must be Stateful object" 420 if __debug__: 421 try: strowner = str(owner) 422 except: strowner = "UNDEF: <%s#%s>" % (owner.__class__, id(owner)) 423 debug("ST", "Setting owner for %s to be %s" % (self, strowner)) 424 if not self.__owner is None: 425 # Remove attributes which were registered to that owner previousely 426 self._updateOwner(register=False) 427 self.__owner = owner 428 if not self.__owner is None: 429 self._updateOwner(register=True)
430 431
432 - def _updateOwner(self, index=None, register=True):
433 """Define an entry within owner's __dict__ 434 so ipython could easily complete it 435 436 :Parameters: 437 index : basestring or list of basestring 438 Name of the attribute. If None -- all known get registered 439 register : bool 440 Register if True or unregister if False 441 442 XXX Needs refactoring since we duplicate the logic of expansion of index value 443 """ 444 if not index is None: 445 if not index in self._items: 446 raise ValueError, \ 447 "Attribute %s is not known to %s" % (index, self) 448 indexes = [ index ] 449 else: 450 indexes = self.names 451 452 ownerdict = self.owner.__dict__ 453 selfdict = self.__dict__ 454 455 for index_ in indexes: 456 if register: 457 if index_ in ownerdict: 458 raise RuntimeError, \ 459 "Cannot register attribute %s within %s " % \ 460 (index_, self.owner) + "since it has one already" 461 ownerdict[index_] = self._items[index_] 462 if index_ in selfdict: 463 raise RuntimeError, \ 464 "Cannot register attribute %s within %s " % \ 465 (index_, self) + "since it has one already" 466 selfdict[index_] = self._items[index_] 467 else: 468 if index_ in ownerdict: 469 # yoh doesn't think that we need to complain if False 470 ownerdict.pop(index_) 471 if index_ in selfdict: 472 selfdict.pop(index_)
473 474 475 # Properties 476 names = property(fget=_getNames) 477 items = property(fget=lambda x:x._items) 478 owner = property(fget=_getOwner, fset=_setOwner) 479 480 # Virtual properties 481 listing = VProperty(fget=_getListing)
482
483 484 -class ParameterCollection(Collection):
485 """Container of Parameters for a stateful object. 486 """ 487 488 # def __init__(self, items=None, owner=None, name=None): 489 # """Initialize the state variables of a derived class 490 # 491 # :Parameters: 492 # items : dict 493 # dictionary of states 494 # """ 495 # Collection.__init__(self, items, owner, name) 496 # 497
498 - def resetvalue(self, index, missingok=False):
499 """Reset all parameters to default values""" 500 from param import Parameter 501 self._action(index, Parameter.resetvalue, missingok=False)
502 503
504 - def isSet(self, index=None):
505 if not index is None: 506 return Collection.isSet(self, index) 507 # go through all members and if any isSet -- return True 508 for index in self._items: 509 if Collection.isSet(self, index): 510 return True 511 return False
512
513 - def whichSet(self):
514 """Return list of indexes which were set""" 515 result = [] 516 # go through all members and if any isSet -- return True 517 for index in self._items: 518 if Collection.isSet(self, index): 519 result.append(index) 520 return result
521
522 523 -class StateCollection(Collection):
524 """Container of StateVariables for a stateful object. 525 526 :Groups: 527 - `Public Access Functions`: `isKnown`, `isEnabled`, `isActive` 528 - `Access Implementors`: `_getListing`, `_getNames`, `_getEnabled` 529 - `Mutators`: `__init__`, `enable`, `disable`, `_setEnabled` 530 - `R/O Properties`: `listing`, `names`, `items` 531 - `R/W Properties`: `enabled` 532 """ 533
534 - def __init__(self, items=None, owner=None):
535 """Initialize the state variables of a derived class 536 537 :Parameters: 538 items : dict 539 dictionary of states 540 owner : Stateful 541 object which owns the collection 542 name : basestring 543 literal description. Usually just attribute name for the 544 collection, e.g. 'states' 545 """ 546 Collection.__init__(self, items=items, owner=owner) 547 548 self.__storedTemporarily = [] 549 """List to contain sets of enabled states which were enabled 550 temporarily. 551 """
552 553 # 554 # XXX TODO: figure out if there is a way to define proper 555 # __copy__'s for a hierarchy of classes. Probably we had 556 # to define __getinitargs__, etc... read more... 557 # 558 #def __copy__(self): 559
560 - def _copy_states_(self, fromstate, deep=False):
561 """Copy known here states from `fromstate` object into current object 562 563 Crafted to overcome a problem mentioned above in the comment 564 and is to be called from __copy__ of derived classes 565 566 Probably sooner than later will get proper __getstate__, 567 __setstate__ 568 """ 569 # Bad check... doesn't generalize well... 570 # if not issubclass(fromstate.__class__, self.__class__): 571 # raise ValueError, \ 572 # "Class %s is not subclass of %s, " % \ 573 # (fromstate.__class__, self.__class__) + \ 574 # "thus not eligible for _copy_states_" 575 # TODO: FOR NOW NO TEST! But this beast needs to be fixed... 576 operation = { True: copy.deepcopy, 577 False: copy.copy }[deep] 578 579 if isinstance(fromstate, Stateful): 580 fromstate = fromstate.states 581 582 self.enabled = fromstate.enabled 583 for name in self.names: 584 if fromstate.isKnown(name): 585 self._items[name] = operation(fromstate._items[name])
586 587
588 - def isEnabled(self, index):
589 """Returns `True` if state `index` is enabled""" 590 self._checkIndex(index) 591 return self._items[index].isEnabled
592
593 - def isActive(self, index):
594 """Returns `True` if state `index` is known and is enabled""" 595 return self.isKnown(index) and self.isEnabled(index)
596 597
598 - def enable(self, index, value=True, missingok=False):
599 """Enable state variable given in `index`""" 600 self._action(index, StateVariable.enable, missingok=missingok, 601 value=value)
602
603 - def disable(self, index):
604 """Disable state variable defined by `index` id""" 605 self._action(index, StateVariable.enable, missingok=False, value=False)
606 607 608 # TODO XXX think about some more generic way to grab temporary 609 # snapshot of CollectableAttributes to be restored later on...
610 - def _changeTemporarily(self, enable_states=None, 611 disable_states=None, other=None):
612 """Temporarily enable/disable needed states for computation 613 614 Enable or disable states which are enabled in `other` and listed in 615 `enable _states`. Use `resetEnabledTemporarily` to reset 616 to previous state of enabled. 617 618 `other` can be a Stateful object or StateCollection 619 """ 620 if enable_states == None: 621 enable_states = [] 622 if disable_states == None: 623 disable_states = [] 624 self.__storedTemporarily.append(self.enabled) 625 other_ = other 626 if isinstance(other, Stateful): 627 other = other.states 628 629 if not other is None: 630 # lets take states which are enabled in other but not in 631 # self 632 add_enable_states = list(Set(other.enabled).difference( 633 Set(enable_states)).intersection(self.names)) 634 if len(add_enable_states)>0: 635 if __debug__: 636 debug("ST", 637 "Adding states %s from %s to be enabled temporarily" % 638 (add_enable_states, other_) + 639 " since they are not enabled in %s" % 640 (self)) 641 enable_states += add_enable_states 642 643 # Lets go one by one enabling only disabled once... but could be as 644 # simple as 645 self.enable(enable_states) 646 self.disable(disable_states)
647 648
649 - def _resetEnabledTemporarily(self):
650 """Reset to previousely stored set of enabled states""" 651 if __debug__: 652 debug("ST", "Resetting to previous set of enabled states") 653 if len(self.enabled)>0: 654 self.enabled = self.__storedTemporarily.pop() 655 else: 656 raise ValueError("Trying to restore not-stored list of enabled " \ 657 "states")
658 659
660 - def _getEnabled(self, nondefault=True, invert=False):
661 """Return list of enabled states 662 663 :Parameters: 664 nondefault : bool 665 Either to return also states which are enabled simply by default 666 invert : bool 667 Would invert the meaning, ie would return disabled states 668 """ 669 if invert: 670 fmatch = lambda y: not self.isEnabled(y) 671 else: 672 fmatch = lambda y: self.isEnabled(y) 673 674 if nondefault: 675 ffunc = fmatch 676 else: 677 ffunc = lambda y: fmatch(y) and self._items[y]._defaultenabled != self.isEnabled(y) 678 return filter(ffunc, self.names)
679 680
681 - def _setEnabled(self, indexlist):
682 """Given `indexlist` make only those in the list enabled 683 684 It might be handy to store set of enabled states and then to restore 685 it later on. It can be easily accomplished now:: 686 687 >>> states_enabled = stateful.enabled 688 >>> stateful.enabled = ['blah'] 689 >>> stateful.enabled = states_enabled 690 691 """ 692 for index in self._items.keys(): 693 self.enable(index, index in indexlist)
694 695 696 # Properties 697 enabled = property(fget=_getEnabled, fset=_setEnabled)
698 699 700 # 701 # Helper dictionaries for collector 702 # 703 _known_collections = { 704 'StateVariable': ("states", StateCollection), 705 'Parameter': ("params", ParameterCollection), 706 'KernelParameter': ("kernel_params", ParameterCollection)} 707 708 _col2class = dict(_known_collections.values()) 709 """Mapping from collection name into Collection class""" 710 711 _collections_order = ['params', 'kernel_params', 'states']
712 713 -class collector(type):
714 """Intended to collect and compose StateCollection for any child 715 class of this metaclass 716 """ 717 718
719 - def __init__(cls, name, bases, dict):
720 721 if __debug__: 722 debug("COLR", 723 "Collector call for %s.%s, where bases=%s, dict=%s " \ 724 % (cls, name, bases, dict)) 725 726 super(collector, cls).__init__(name, bases, dict) 727 728 collections = {} 729 for name, value in dict.iteritems(): 730 if isinstance(value, CollectableAttribute): 731 baseclassname = value.__class__.__name__ 732 col = _known_collections[baseclassname][0] 733 # XXX should we allow to throw exceptions here? 734 if not collections.has_key(col): 735 collections[col] = {} 736 collections[col][name] = value 737 # and assign name if not yet was set 738 if value.name is None: 739 value.name = name 740 741 # XXX can we first collect parent's states and then populate with ours? TODO 742 743 for base in bases: 744 if hasattr(base, "__metaclass__") and \ 745 base.__metaclass__ == collector: 746 # TODO take care about overriding one from super class 747 # for state in base.states: 748 # if state[0] = 749 newcollections = base._collections_template 750 if len(newcollections) == 0: 751 continue 752 if __debug__: 753 debug("COLR", 754 "Collect collections %s for %s from %s" % 755 (newcollections, cls, base)) 756 for col, collection in newcollections.iteritems(): 757 newitems = collection.items 758 if collections.has_key(col): 759 collections[col].update(newitems) 760 else: 761 collections[col] = newitems 762 763 764 if __debug__: 765 debug("COLR", 766 "Creating StateCollection template %s" % cls) 767 768 # if there is an explicit 769 if hasattr(cls, "_ATTRIBUTE_COLLECTIONS"): 770 for col in cls._ATTRIBUTE_COLLECTIONS: 771 if not col in _col2class: 772 raise ValueError, \ 773 "Requested collection %s is unknown to collector" % \ 774 col 775 if not col in collections: 776 collections[col] = None 777 778 # TODO: check on conflict in names of Collections' items! 779 # since otherwise even order is not definite since we use dict for collections. 780 # XXX should we switch to tuple? 781 782 for col, colitems in collections.iteritems(): 783 collections[col] = _col2class[col](colitems) 784 785 setattr(cls, "_collections_template", collections) 786 787 # 788 # Expand documentation for the class based on the listed 789 # parameters an if it is stateful 790 # 791 # TODO -- figure nice way on how to alter __init__ doc directly... 792 paramsdoc = "" 793 textwrapper = TextWrapper(subsequent_indent=" ", 794 initial_indent=" ", 795 width=70) 796 797 for col in ('params', 'kernel_params'): 798 if collections.has_key(col): 799 for param, parameter in collections[col].items.iteritems(): 800 paramsdoc += " %s" % param 801 try: 802 paramsdoc += " : %s" % parameter.allowedtype 803 except: 804 pass 805 paramsdoc += "\n" 806 try: 807 doc = parameter.__doc__ 808 try: 809 doc += " (Default: %s)" % parameter.default 810 except: 811 pass 812 paramsdoc += '\n'.join(textwrapper.wrap(doc))+'\n' 813 except Exception, e: 814 pass 815 816 if paramsdoc != "": 817 if __debug__: 818 debug("COLR", "Assigning __paramsdoc to be %s" % paramsdoc) 819 setattr(cls, "_paramsdoc", paramsdoc) 820 821 cls.__doc__ = enhancedClassDocString(cls, *bases)
822
823 824 -class Stateful(object):
825 """Base class for stateful objects. 826 827 Classes inherited from this class gain ability to provide state 828 variables, accessed as simple properties. Access to state variables 829 "internals" is done via states property and interface of 830 `StateCollection`. 831 832 NB This one is to replace old State base class 833 TODO: rename 'descr'? -- it should simply 834 be 'doc' -- no need to drag classes docstring imho. 835 """ 836 837 __metaclass__ = collector 838 839 _initargs = [ 'enable_states', 'disable_states', 'descr' ] 840 """Initialization parameters which should be passed to Statefull""" 841
842 - def __init__(self, 843 enable_states=None, 844 disable_states=None, 845 descr=None):
846 """Initialize Stateful object 847 848 :Parameters: 849 enable_states : None or list of basestring 850 Names of the state variables which should be enabled additionally 851 to default ones 852 disable_states : None or list of basestring 853 Names of the state variables which should be disabled 854 descr : basestring 855 Description of the instance 856 """ 857 if not hasattr(self, '_collections'): 858 # need to check to avoid override of enabled states in the case 859 # of multiple inheritance, like both Statefull and Harvestable 860 object.__setattr__(self, '_collections', 861 copy.deepcopy( \ 862 object.__getattribute__(self, 863 '_collections_template'))) 864 865 # Assign owner to all collections 866 for col, collection in self._collections.iteritems(): 867 if col in self.__dict__: 868 raise ValueError, \ 869 "Stateful object %s has already attribute %s" % \ 870 (self, col) 871 self.__dict__[col] = collection 872 collection.owner = self 873 874 if self._collections.has_key('states'): 875 if enable_states == None: 876 enable_states = [] 877 if disable_states == None: 878 disable_states = [] 879 880 states = self._collections['states'] 881 states.enable(enable_states, missingok=True) 882 states.disable(disable_states) 883 elif not (enable_states is None and disable_states is None): 884 warning("Provided enable_states and disable_states are " + \ 885 "ignored since object %s has no states" % `self`) 886 887 self.__descr = descr 888 889 if __debug__: 890 debug("ST", "Stateful.__init__ was done for %s id %s with descr=%s" \ 891 % (self.__class__, id(self), descr))
892 893 894 #__doc__ = enhancedDocString('Stateful', locals()) 895 896
897 - def __getattribute__(self, index):
898 # return all private ones first since smth like __dict__ might be 899 # queried by copy before instance is __init__ed 900 if index[0] == '_': 901 return object.__getattribute__(self, index) 902 for col, collection in object.__getattribute__(self, '_collections').iteritems(): 903 if index in [col]: 904 return collection 905 if collection.items.has_key(index): 906 return collection.getvalue(index) 907 return object.__getattribute__(self, index)
908
909 - def __setattr__(self, index, value):
910 if index[0] == '_': 911 return object.__setattr__(self, index, value) 912 for col, collection in object.__getattribute__(self, '_collections').iteritems(): 913 if collection.items.has_key(index): 914 collection.setvalue(index, value) 915 return 916 object.__setattr__(self, index, value)
917 918 # XXX not sure if we shouldn't implement anything else...
919 - def reset(self):
920 for collection in self._collections.values(): 921 collection.reset()
922
923 - def __str__(self):
924 s = "%s:" % (self.__class__.__name__) 925 if hasattr(self, "_collections"): 926 for col, collection in self._collections.iteritems(): 927 s += " %d %s:%s" %(len(collection.items), col, str(collection)) 928 return s
929
930 - def __repr__(self):
931 return "<%s.%s#%d>" % (self.__class__.__module__, self.__class__.__name__, id(self))
932 933 descr = property(lambda self: self.__descr, 934 doc="Description of the object if any")
935
936 937 938 -class Harvestable(Stateful):
939 """Classes inherited from this class intend to collect attributes 940 within internal processing. 941 942 Subclassing Harvestable we gain ability to collect any internal 943 data from the processing which is especially important if an 944 object performs something in loop and discards some intermidiate 945 possibly interesting results (like in case of 946 CrossValidatedTransferError and states of the trained classifier 947 or TransferError). 948 949 """ 950 951 harvested = StateVariable(enabled=False, doc= 952 """Store specified attributes of classifiers at each split""") 953 954 _KNOWN_COPY_METHODS = [ None, 'copy', 'deepcopy' ] 955
956 - def __init__(self, attribs=None, copy_attribs='copy', **kwargs):
957 """Initialize state of harvestable 958 959 :Parameters: 960 attribs : list of basestr or dicts 961 What attributes of call to store and return within 962 harvested state variable. If an item is a dictionary, 963 following keys are used ['name', 'copy'] 964 copy_attribs : None or basestr 965 Default copying. If None -- no copying, 'copy' 966 - shallow copying, 'deepcopy' -- deepcopying 967 968 """ 969 Stateful.__init__(self, **kwargs) 970 971 self.__atribs = attribs 972 self.__copy_attribs = copy_attribs 973 974 self._setAttribs(attribs)
975
976 - def _setAttribs(self, attribs):
977 """Set attributes to harvest 978 979 Each attribute in self.__attribs must have following fields 980 - name : functional (or arbitrary if 'obj' or 'attr' is set) 981 description of the thing to harvest, 982 e.g. 'transerror.clf.training_time' 983 - obj : name of the object to harvest from (if empty, 984 'self' is assumed), 985 e.g 'transerror' 986 - attr : attribute of 'obj' to harvest, 987 e.g. 'clf.training_time' 988 - copy : None, 'copy' or 'deepcopy' - way to copy attribute 989 """ 990 if attribs: 991 # force the state 992 self.states.enable('harvested') 993 self.__attribs = [] 994 for i, attrib in enumerate(attribs): 995 if isinstance(attrib, dict): 996 if not 'name' in attrib: 997 raise ValueError, \ 998 "Harvestable: attribute must be a string or " + \ 999 "a dictionary with 'name'" 1000 else: 1001 attrib = {'name': attrib} 1002 1003 # assign default method to copy 1004 if not 'copy' in attrib: 1005 attrib['copy'] = self.__copy_attribs 1006 1007 # check copy method 1008 if not attrib['copy'] in self._KNOWN_COPY_METHODS: 1009 raise ValueError, "Unknown method %s. Known are %s" % \ 1010 (attrib['copy'], self._KNOWN_COPY_METHODS) 1011 1012 if not ('obj' in attrib or 'attr' in attrib): 1013 # Process the item to harvest 1014 # split into obj, attr. If obj is empty, then assume self 1015 split = attrib['name'].split('.', 1) 1016 if len(split)==1: 1017 obj, attr = split[0], None 1018 else: 1019 obj, attr = split 1020 attrib.update({'obj':obj, 'attr':attr}) 1021 1022 if attrib['obj'] == '': 1023 attrib['obj'] = 'self' 1024 1025 # TODO: may be enabling of the states?? 1026 1027 self.__attribs.append(attrib) # place value back 1028 else: 1029 # just to make sure it is not None or 0 1030 self.__attribs = []
1031 1032
1033 - def _harvest(self, vars):
1034 """The harvesting function: must obtain dictionary of variables from the caller. 1035 1036 :Parameters: 1037 vars : dict 1038 Dictionary of available data. Most often locals() could be 1039 passed as `vars`. Mention that desired to be harvested 1040 private attributes better be bound locally to some variable 1041 1042 :Returns: 1043 nothing 1044 """ 1045 1046 if not self.states.isEnabled('harvested') or len(self.__attribs)==0: 1047 return 1048 1049 if not self.states.isSet('harvested'): 1050 self.harvested = dict([(a['name'], []) for a in self.__attribs]) 1051 1052 for attrib in self.__attribs: 1053 attrv = vars[attrib['obj']] 1054 1055 # access particular attribute if needed 1056 if not attrib['attr'] is None: 1057 attrv = eval('attrv.%s' % attrib['attr']) 1058 1059 # copy the value if needed 1060 attrv = {'copy':copy.copy, 1061 'deepcopy':copy.deepcopy, 1062 None:lambda x:x}[attrib['copy']](attrv) 1063 1064 self.harvested[attrib['name']].append(attrv)
1065 1066 1067 harvest_attribs = property(fget=lambda self:self.__attribs, 1068 fset=_setAttribs)
1069
1070 1071 -class Parametrized(Stateful):
1072 """Base class for all classes which have collected parameters 1073 """ 1074
1075 - def __init__(self, init_classes=None, **kwargs):
1076 """Initialize Parametrized class instance 1077 1078 :Parameters: 1079 init_classes : list of class 1080 List of classes which should be called with arguments which 1081 were not handled by Parametrized 1082 """ 1083 # compose kwargs to be passed to Stateful and remove them from kwargs 1084 kwargs_stateful = {} 1085 for arg in Stateful._initargs: 1086 if kwargs.has_key(arg): 1087 kwargs_stateful[arg] = kwargs.pop(arg) 1088 1089 # initialize Stateful with only needed parameters 1090 Stateful.__init__(self, **kwargs_stateful) 1091 1092 # take only relevant collections 1093 collections = filter(lambda x:isinstance(x, ParameterCollection), 1094 self._collections.values()) 1095 1096 # assign given parameters 1097 for arg, argument in kwargs.items(): 1098 set = False 1099 for collection in collections: 1100 if collection.items.has_key(arg): 1101 collection.setvalue(arg, argument) 1102 set = True 1103 break 1104 if set: 1105 trash = kwargs.pop(arg) 1106 #if not set: 1107 # known_params = reduce(lambda x,y:x+y, [x.items.keys() for x in collections], []) 1108 # raise TypeError, \ 1109 # "Unknown parameter %s=%s for %s." % (arg, argument, self) \ 1110 # + " Valid parameters are %s" % known_params 1111 1112 # Initialize other base classes 1113 if init_classes is not None: 1114 # return back stateful arguments since they might be 1115 # processed by underlying classes 1116 kwargs.update(kwargs_stateful) 1117 for cls in init_classes: 1118 cls.__init__(self, **kwargs) 1119 else: 1120 if len(kwargs)>0: 1121 known_params = reduce(lambda x,y:x+y, [x.items.keys() for x in collections], []) 1122 raise TypeError, \ 1123 "Unknown parameters %s for %s." % (kwargs.keys(), self) \ 1124 + " Valid parameters are %s" % known_params
1125 1126
1127 - def __repr__(self, fullname=True):
1128 """Definition of the object summary over Parametrized object 1129 """ 1130 # XXX res = "%s(kernel_type='%s'" % (self.__class__.__name__, self._kernel_type_literal) 1131 res = "" 1132 if fullname: 1133 modulename = '%s' % self.__class__.__module__ 1134 if modulename != "__main__": 1135 res = "%s." % modulename 1136 res += "%s(" % (self.__class__.__name__) 1137 1138 sep = "" 1139 collections = self._collections 1140 # we want them in this particular order 1141 for col in _collections_order: 1142 if not collections.has_key(col): 1143 continue 1144 collection = collections[col] 1145 if isinstance(collection, ParameterCollection): 1146 for k in collection.names: 1147 # list only params with not default values 1148 if collection[k].isDefault: continue 1149 res += "%s%s=%s" % (sep, k, collection[k].value) 1150 sep = ', ' 1151 elif isinstance(collection, StateCollection): 1152 for name, invert in ( ('enable', False), ('disable', True) ): 1153 states = collection._getEnabled(nondefault=False, invert=invert) 1154 if len(states): 1155 res += sep + "%s_states=%s" % (name, str(states)) 1156 else: 1157 raise RuntimeError, "Unknown type of collection %s" % col 1158 res += ")" 1159 return res
1160
1161 - def __str__(self):
1162 return self.__repr__(fullname=False)
1163