1
2
3
4
5
6
7
8
9 """Classes to control and store state information.
10
11 It was devised to provide conditional storage
12 """
13
14
15
16
17
18 __docformat__ = 'restructuredtext'
19
20 import operator, copy
21 from sets import Set
22 from textwrap import TextWrapper
23
24 import numpy as N
25
26 from mvpa.misc.vproperty import VProperty
27 from mvpa.misc.exceptions import UnknownStateError
28 from mvpa.misc.attributes import CollectableAttribute, StateVariable
29 from mvpa.base.dochelpers import enhancedDocString
30
31 from mvpa.base import externals
32
33 if __debug__:
34 from mvpa.base import debug
35
36
37 _in_ipython = externals.exists('running ipython env')
38
39
40 _def_sep = ('`', '')[int(_in_ipython)]
41
42 _object_getattribute = object.__getattribute__
43 _object_setattr = object.__setattr__
44
45
46
47
48
49
50
51
53 """Container of some CollectableAttributes.
54
55 :Groups:
56 - `Public Access Functions`: `isKnown`
57 - `Access Implementors`: `_getListing`, `_getNames`
58 - `Mutators`: `__init__`
59 - `R/O Properties`: `listing`, `names`, `items`
60
61 XXX Seems to be not used and duplicating functionality: `_getListing`
62 (thus `listing` property)
63 """
64
65 - def __init__(self, items=None, owner=None, name=None):
66 """Initialize the Collection
67
68 :Parameters:
69 items : dict of CollectableAttribute's
70 items to initialize with
71 owner : object
72 an object to which collection belongs
73 name : basestring
74 name of the collection (as seen in the owner, e.g. 'states')
75 """
76
77 self.__owner = owner
78
79 if items == None:
80 items = {}
81 self._items = items
82 """Dictionary to contain registered states as keys and
83 values signal either they are enabled
84 """
85 self.__name = name
86
89
91 num = len(self._items)
92 if __debug__ and "ST" in debug.active:
93 maxnumber = 1000
94 else:
95 maxnumber = 4
96 if self.__name is not None:
97 res = self.__name
98 else:
99 res = ""
100 res += "{"
101 for i in xrange(min(num, maxnumber)):
102 if i > 0:
103 res += " "
104 res += "%s" % str(self._items.values()[i])
105 if len(self._items) > maxnumber:
106 res += "..."
107 res += "}"
108 if __debug__:
109 if "ST" in debug.active:
110 res += " owner:%s#%s" % (self.owner.__class__.__name__,
111 id(self.owner))
112 return res
113
114
116 """Collection specific part of __repr__ for a class containing
117 it, ie a part of __repr__ for the owner object
118
119 :Return:
120 list of items to be appended within __repr__ after a .join()
121 """
122
123
124 raise NotImplementedError, "Class %s should override _cls_repr" \
125 % self.__class__.__name__
126
128 """Checks if index could be assigned within collection via
129 _initialize
130
131 :Return: bool value for a given `index`
132
133 It is to facilitate dynamic assignment of collections' items
134 within derived classes' __init__ depending on the present
135 collections in the class.
136 """
137
138
139
140
141
142
143
144
145
146 return index in self._items.keys()
147
148
150 """Initialize `index` (no check performed) with `value`
151 """
152
153 self[index].value = value
154
155
157 s = "%s(" % self.__class__.__name__
158 items_s = ""
159 sep = ""
160 for item in self._items:
161 try:
162 itemvalue = "%s" % `self._items[item].value`
163 if len(itemvalue)>50:
164 itemvalue = itemvalue[:10] + '...' + itemvalue[-10:]
165 items_s += "%s'%s':%s" % (sep, item, itemvalue)
166 sep = ', '
167 except:
168 pass
169 if items_s != "":
170 s += "items={%s}" % items_s
171 if self.owner is not None:
172 s += "%sowner=%s" % (sep, `self.owner`)
173 s += ")"
174 return s
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
213 """Returns `True` if state `index` is known at all"""
214 return self._items.has_key(index)
215
216
217 - def isSet(self, index=None):
218 """If item (or any in the present or listed) was set
219
220 :Parameters:
221 index : None or basestring or list of basestring
222 What items to check if they were set in the collection
223 """
224 _items = self._items
225 if not (index is None):
226 if isinstance(index, basestring):
227 self._checkIndex(index)
228 return _items[index].isSet
229 else:
230 items = index
231 else:
232 items = self._items
233
234 for index in items:
235 self._checkIndex(index)
236 if _items[index].isSet:
237 return True
238 return False
239
240
242 """Return list of indexes which were set"""
243 result = []
244
245 for index,v in self._items.iteritems():
246 if v.isSet:
247 result.append(index)
248 return result
249
250
252 """Verify that given `index` is a known/registered state.
253
254 :Raise `KeyError`: if given `index` is not known
255 """
256
257
258 if not self._items.has_key(index):
259 raise KeyError, \
260 "%s of %s has no key '%s' registered" \
261 % (self.__class__.__name__,
262 self.__owner.__class__.__name__,
263 index)
264
265
266 - def add(self, item):
267 """Add a new CollectableAttribute to the collection
268
269 :Parameters:
270 item : CollectableAttribute
271 or of derived class. Must have 'name' assigned
272
273 TODO: we should make it stricter to don't add smth of
274 wrong type into Collection since it might lead to problems
275
276 Also we might convert to __setitem__
277 """
278
279 name = item.name
280 if not isinstance(item, CollectableAttribute):
281 raise ValueError, \
282 "Collection can add only instances of " + \
283 "CollectableAttribute-derived classes. Got %s" % `item`
284
285 if name is None:
286 raise ValueError, \
287 "CollectableAttribute to be added %s must have 'name' set" % \
288 item
289 self._items[name] = item
290
291 if not self.owner is None:
292 self._updateOwner(name)
293
294
301
302
304 """
305 """
306
307
308 if index[0] == '_':
309 return _object_getattribute(self, index)
310 _items = _object_getattribute(self, '_items')
311 if index in _items:
312 return _items[index].value
313 return _object_getattribute(self, index)
314
315
317 if index[0] == '_':
318 return _object_setattr(self, index, value)
319 _items = _object_getattribute(self, '_items')
320 if index in _items:
321 _items[index].value = value
322 else:
323 _object_setattr(self, index, value)
324
325
327 _items = _object_getattribute(self, '_items')
328 if index in _items:
329 self._checkIndex(index)
330 return _items[index]
331 else:
332 raise AttributeError("State collection %s has no %s attribute"
333 % (self, index))
334
335
336
337
338
339
340
341
342
343
344
345
346
347 - def get(self, index, default):
348 """Access the value by a given index.
349
350 Mimiquing regular dictionary behavior, if value cannot be obtained
351 (i.e. if any exception is caught) return default value.
352 """
353 try:
354 return self[index].value
355 except Exception, e:
356
357 return default
358
359
360
361
362 - def _action(self, index, func, missingok=False, **kwargs):
363 """Run specific func either on a single item or on all of them
364
365 :Parameters:
366 index : basestr
367 Name of the state variable
368 func
369 Function (not bound) to call given an item, and **kwargs
370 missingok : bool
371 If True - do not complain about wrong index
372 """
373 if isinstance(index, basestring):
374 if index.upper() == 'ALL':
375 for index_ in self._items:
376 self._action(index_, func, missingok=missingok, **kwargs)
377 else:
378 try:
379 self._checkIndex(index)
380 func(self._items[index], **kwargs)
381 except:
382 if missingok:
383 return
384 raise
385 elif operator.isSequenceType(index):
386 for item in index:
387 self._action(item, func, missingok=missingok, **kwargs)
388 else:
389 raise ValueError, \
390 "Don't know how to handle variable given by %s" % index
391
392
393 - def reset(self, index=None):
406
407
409 """Return a list of registered states along with the documentation"""
410
411
412 items = self._items.items()
413 items.sort()
414 return [ "%s%s%s: %s" % (_def_sep, str(x[1]), _def_sep, x[1].__doc__)
415 for x in items ]
416
417
419 """Return ids for all registered state variables"""
420 return self._items.keys()
421
422
425
426
428 if not isinstance(owner, ClassWithCollections):
429 raise ValueError, \
430 "Owner of the StateCollection must be ClassWithCollections object"
431 if __debug__:
432 try: strowner = str(owner)
433 except: strowner = "UNDEF: <%s#%s>" % (owner.__class__, id(owner))
434 debug("ST", "Setting owner for %s to be %s" % (self, strowner))
435 if not self.__owner is None:
436
437 self._updateOwner(register=False)
438 self.__owner = owner
439 if not self.__owner is None:
440 self._updateOwner(register=True)
441
442
444 """Define an entry within owner's __dict__
445 so ipython could easily complete it
446
447 :Parameters:
448 index : basestring or list of basestring
449 Name of the attribute. If None -- all known get registered
450 register : bool
451 Register if True or unregister if False
452
453 XXX Needs refactoring since we duplicate the logic of expansion of
454 index value
455 """
456 if not index is None:
457 if not index in self._items:
458 raise ValueError, \
459 "Attribute %s is not known to %s" % (index, self)
460 indexes = [ index ]
461 else:
462 indexes = self.names
463
464 ownerdict = self.owner.__dict__
465 selfdict = self.__dict__
466 owner_known = ownerdict['_known_attribs']
467 for index_ in indexes:
468 if register:
469 if index_ in ownerdict:
470 raise RuntimeError, \
471 "Cannot register attribute %s within %s " % \
472 (index_, self.owner) + "since it has one already"
473 ownerdict[index_] = self._items[index_]
474 if index_ in selfdict:
475 raise RuntimeError, \
476 "Cannot register attribute %s within %s " % \
477 (index_, self) + "since it has one already"
478 selfdict[index_] = self._items[index_]
479 owner_known[index_] = self.__name
480 else:
481 if index_ in ownerdict:
482
483 ownerdict.pop(index_)
484 owner_known.pop(index_)
485 if index_ in selfdict:
486 selfdict.pop(index_)
487
488
489
490 names = property(fget=_getNames)
491 items = property(fget=lambda x:x._items)
492 owner = property(fget=_getOwner, fset=_setOwner)
493 name = property(fget=lambda x:x.__name, fset=_setName)
494
495
496 listing = VProperty(fget=_getListing)
497
498
499
501 """Container of Parameters for a stateful object.
502 """
503
504
505
506
507
508
509
510
511
512
513
515 """Part of __repr__ for the owner object
516 """
517 prefixes = []
518 for k in self.names:
519
520 if self[k].isDefault:
521 continue
522 prefixes.append("%s=%s" % (k, self[k].value))
523 return prefixes
524
525
530
531
533 """Container for data and attributes of samples (ie data/labels/chunks/...)
534 """
535
536
537
538
539
540
541
542
543
544
545
547 """Part of __repr__ for the owner object
548 """
549 return []
550
551
552
554 """Container of StateVariables for a stateful object.
555
556 :Groups:
557 - `Public Access Functions`: `isKnown`, `isEnabled`, `isActive`
558 - `Access Implementors`: `_getListing`, `_getNames`, `_getEnabled`
559 - `Mutators`: `__init__`, `enable`, `disable`, `_setEnabled`
560 - `R/O Properties`: `listing`, `names`, `items`
561 - `R/W Properties`: `enabled`
562 """
563
564 - def __init__(self, items=None, owner=None):
565 """Initialize the state variables of a derived class
566
567 :Parameters:
568 items : dict
569 dictionary of states
570 owner : ClassWithCollections
571 object which owns the collection
572 name : basestring
573 literal description. Usually just attribute name for the
574 collection, e.g. 'states'
575 """
576 Collection.__init__(self, items=items, owner=owner)
577
578 self.__storedTemporarily = []
579 """List to contain sets of enabled states which were enabled
580 temporarily.
581 """
582
583
584
585
586
587
588
589
591 """Part of __repr__ for the owner object
592 """
593 prefixes = []
594 for name, invert in ( ('enable', False), ('disable', True) ):
595 states = self._getEnabled(nondefault=False,
596 invert=invert)
597 if len(states):
598 prefixes.append("%s_states=%s" % (name, str(states)))
599 return prefixes
600
601
603 """Checks if index could be assigned within collection via
604 setvalue
605 """
606 return index in ['enable_states', 'disable_states']
607
608
610 if value is None:
611 value = []
612 if index == 'enable_states':
613 self.enable(value, missingok=True)
614 elif index == 'disable_states':
615 self.disable(value)
616 else:
617 raise ValueError, "StateCollection can accept only enable_states " \
618 "and disable_states arguments for the initialization. " \
619 "Got %s" % index
620
621
623 """Copy known here states from `fromstate` object into current object
624
625 :Parameters:
626 fromstate : Collection or ClassWithCollections
627 Source states to copy from
628 index : None or list of basestring
629 If not to copy all set state variables, index provides
630 selection of what to copy
631 deep : bool
632 Optional control over the way to copy
633
634 Crafted to overcome a problem mentioned above in the comment
635 and is to be called from __copy__ of derived classes
636
637 Probably sooner than later will get proper __getstate__,
638 __setstate__
639 """
640
641
642
643
644
645
646
647 operation = { True: copy.deepcopy,
648 False: copy.copy }[deep]
649
650 if isinstance(fromstate, ClassWithCollections):
651 fromstate = fromstate.states
652
653
654 _items, from_items = self._items, fromstate._items
655 if index is None:
656
657 for name in fromstate.whichSet():
658
659 _items[name] = operation(from_items[name])
660 else:
661 isKnown = fromstate.isKnown
662 for name in index:
663 if isKnown(name):
664 _items[name] = operation(from_items[name])
665
666
671
672
676
677
678 - def enable(self, index, value=True, missingok=False):
682
683
687
688
689
690
693 """Temporarily enable/disable needed states for computation
694
695 Enable or disable states which are enabled in `other` and listed in
696 `enable _states`. Use `resetEnabledTemporarily` to reset
697 to previous state of enabled.
698
699 `other` can be a ClassWithCollections object or StateCollection
700 """
701 if enable_states == None:
702 enable_states = []
703 if disable_states == None:
704 disable_states = []
705 self.__storedTemporarily.append(self.enabled)
706 other_ = other
707 if isinstance(other, ClassWithCollections):
708 other = other.states
709
710 if not other is None:
711
712
713 add_enable_states = list(Set(other.enabled).difference(
714 Set(enable_states)).intersection(self.names))
715 if len(add_enable_states)>0:
716 if __debug__:
717 debug("ST",
718 "Adding states %s from %s to be enabled temporarily" %
719 (add_enable_states, other_) +
720 " since they are not enabled in %s" %
721 (self))
722 enable_states += add_enable_states
723
724
725
726 self.enable(enable_states)
727 self.disable(disable_states)
728
729
731 """Reset to previousely stored set of enabled states"""
732 if __debug__:
733 debug("ST", "Resetting to previous set of enabled states")
734 if len(self.enabled)>0:
735 self.enabled = self.__storedTemporarily.pop()
736 else:
737 raise ValueError("Trying to restore not-stored list of enabled " \
738 "states")
739
740
742 """Return list of enabled states
743
744 :Parameters:
745 nondefault : bool
746 Either to return also states which are enabled simply by default
747 invert : bool
748 Would invert the meaning, ie would return disabled states
749 """
750 if invert:
751 fmatch = lambda y: not self.isEnabled(y)
752 else:
753 fmatch = lambda y: self.isEnabled(y)
754
755 if nondefault:
756 ffunc = fmatch
757 else:
758 ffunc = lambda y: fmatch(y) and \
759 self._items[y]._defaultenabled != self.isEnabled(y)
760 return filter(ffunc, self.names)
761
762
764 """Given `indexlist` make only those in the list enabled
765
766 It might be handy to store set of enabled states and then to restore
767 it later on. It can be easily accomplished now::
768
769 >>> from mvpa.misc.state import ClassWithCollections, StateVariable
770 >>> class Blah(ClassWithCollections):
771 ... bleh = StateVariable(enabled=False, doc='Example')
772 ...
773 >>> blah = Blah()
774 >>> states_enabled = blah.states.enabled
775 >>> blah.states.enabled = ['bleh']
776 >>> blah.states.enabled = states_enabled
777 """
778 for index in self._items.keys():
779 self.enable(index, index in indexlist)
780
781
782
783 enabled = property(fget=_getEnabled, fset=_setEnabled)
784
785
786
787
788
789
790
791
792
793
794 _known_collections = {
795
796 'StateVariable': ("states", StateCollection),
797
798 'Parameter': ("params", ParameterCollection),
799 'KernelParameter': ("kernel_params", ParameterCollection),
800
801
802 'SampleAttribute': ("sa", SampleAttributesCollection),
803 'FeatureAttribute': ("fa", SampleAttributesCollection),
804 'DatasetAttribute': ("dsa", SampleAttributesCollection),
805 }
806
807
808 _col2class = dict(_known_collections.values())
809 """Mapping from collection name into Collection class"""
810
811
812 _COLLECTIONS_ORDER = ['sa', 'fa', 'dsa',
813 'params', 'kernel_params', 'states']
814
815
817 """Intended to collect and compose StateCollection for any child
818 class of this metaclass
819 """
820
821
823
824 if __debug__:
825 debug(
826 "COLR",
827 "AttributesCollector call for %s.%s, where bases=%s, dict=%s " \
828 % (cls, name, bases, dict))
829
830 super(AttributesCollector, cls).__init__(name, bases, dict)
831
832 collections = {}
833 for name, value in dict.iteritems():
834 if isinstance(value, CollectableAttribute):
835 baseclassname = value.__class__.__name__
836 col = _known_collections[baseclassname][0]
837
838 if not collections.has_key(col):
839 collections[col] = {}
840 collections[col][name] = value
841
842 if value.name is None:
843 value._setName(name)
844
845
846
847 delattr(cls, name)
848
849
850
851
852 for base in bases:
853 if hasattr(base, "__metaclass__") and \
854 base.__metaclass__ == AttributesCollector:
855
856
857
858 newcollections = base._collections_template
859 if len(newcollections) == 0:
860 continue
861 if __debug__:
862 debug("COLR",
863 "Collect collections %s for %s from %s" %
864 (newcollections, cls, base))
865 for col, collection in newcollections.iteritems():
866 newitems = collection.items
867 if collections.has_key(col):
868 collections[col].update(newitems)
869 else:
870 collections[col] = newitems
871
872
873 if __debug__:
874 debug("COLR",
875 "Creating StateCollection template %s with collections %s"
876 % (cls, collections.keys()))
877
878
879 if hasattr(cls, "_ATTRIBUTE_COLLECTIONS"):
880 for col in cls._ATTRIBUTE_COLLECTIONS:
881 if not col in _col2class:
882 raise ValueError, \
883 "Requested collection %s is unknown to collector" % \
884 col
885 if not col in collections:
886 collections[col] = None
887
888
889
890
891
892
893 for col, colitems in collections.iteritems():
894 collections[col] = _col2class[col](colitems)
895
896 setattr(cls, "_collections_template", collections)
897
898
899
900
901
902
903 textwrapper = TextWrapper(subsequent_indent=" ",
904 initial_indent=" ",
905 width=70)
906
907
908 paramsdoc = ""
909 paramscols = []
910 for col in ('params', 'kernel_params'):
911 if collections.has_key(col):
912 paramscols.append(col)
913
914 col_items = collections[col].items
915 params = [(v._instance_index, k) for k,v in col_items.iteritems()]
916 params.sort()
917 paramsdoc += '\n'.join(
918 [col_items[param].doc(indent=' ')
919 for index,param in params]) + '\n'
920
921
922
923 setattr(cls, "_paramscols", paramscols)
924
925
926 statesdoc = ""
927 if collections.has_key('states'):
928 paramsdoc += """ enable_states : None or list of basestring
929 Names of the state variables which should be enabled additionally
930 to default ones
931 disable_states : None or list of basestring
932 Names of the state variables which should be disabled
933 """
934 statesdoc = " * "
935 statesdoc += '\n * '.join(collections['states'].listing)
936 statesdoc += "\n\n(States enabled by default are listed with `+`)"
937 if __debug__:
938 debug("COLR", "Assigning __statesdoc to be %s" % statesdoc)
939 setattr(cls, "_statesdoc", statesdoc)
940
941 if paramsdoc != "":
942 if __debug__ and 'COLR' in debug.active:
943 debug("COLR", "Assigning __paramsdoc to be %s" % paramsdoc)
944 setattr(cls, "_paramsdoc", paramsdoc)
945
946 if paramsdoc + statesdoc != "":
947 cls.__doc__ = enhancedDocString(cls, *bases)
948
949
950
952 """Base class for objects which contain any known collection
953
954 Classes inherited from this class gain ability to access
955 collections and their items as simple attributes. Access to
956 collection items "internals" is done via <collection_name> attribute
957 and interface of a corresponding `Collection`.
958 """
959
960 _DEV__doc__ = """
961 TODO: rename 'descr'? -- it should simply
962 be 'doc' -- no need to drag classes docstring imho.
963 """
964
965 __metaclass__ = AttributesCollector
966
967 - def __new__(cls, *args, **kwargs):
968 """Initialize ClassWithCollections object
969
970 :Parameters:
971 descr : basestring
972 Description of the instance
973 """
974 self = super(ClassWithCollections, cls).__new__(cls)
975
976 s__dict__ = self.__dict__
977
978
979
980
981 self.__params_set = False
982
983
984
985 if not s__dict__.has_key('_collections'):
986 s__class__ = self.__class__
987
988 collections = copy.deepcopy(s__class__._collections_template)
989 s__dict__['_collections'] = collections
990 s__dict__['_known_attribs'] = {}
991 """Dictionary to contain 'links' to the collections from each
992 known attribute. Is used to gain some speed up in lookup within
993 __getattribute__ and __setattr__
994 """
995
996
997 for col, collection in collections.iteritems():
998 if col in s__dict__:
999 raise ValueError, \
1000 "Object %s has already attribute %s" % \
1001 (self, col)
1002 s__dict__[col] = collection
1003 collection.name = col
1004 collection.owner = self
1005
1006 self.__params_set = False
1007
1008 if __debug__:
1009 descr = kwargs.get('descr', None)
1010 debug("COL", "ClassWithCollections.__new__ was done "
1011 "for %s#%s with descr=%s" \
1012 % (s__class__.__name__, id(self), descr))
1013
1014 return self
1015
1016
1017 - def __init__(self, descr=None, **kwargs):
1018
1019 if not self.__params_set:
1020 self.__descr = descr
1021 """Set humane description for the object"""
1022
1023
1024 self.__params_set = True
1025
1026 collections = self._collections
1027
1028
1029 for arg, argument in kwargs.items():
1030 set = False
1031 for collection in collections.itervalues():
1032 if collection._is_initializable(arg):
1033 collection._initialize(arg, argument)
1034 set = True
1035 break
1036 if set:
1037 trash = kwargs.pop(arg)
1038 else:
1039 known_params = reduce(
1040 lambda x,y:x+y,
1041 [x.items.keys() for x in collections.itervalues()], [])
1042 raise TypeError, \
1043 "Unexpected keyword argument %s=%s for %s." \
1044 % (arg, argument, self) \
1045 + " Valid parameters are %s" % known_params
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064 if __debug__:
1065 debug("COL", "ClassWithCollections.__init__ was done "
1066 "for %s#%s with descr=%s" \
1067 % (self.__class__.__name__, id(self), descr))
1068
1069
1070
1071
1072
1074
1075
1076 if index[0] == '_':
1077 return _object_getattribute(self, index)
1078
1079 s_dict = _object_getattribute(self, '__dict__')
1080
1081 collections = s_dict['_collections']
1082 if index in collections:
1083 return collections[index]
1084
1085
1086 known_attribs = s_dict['_known_attribs']
1087 if index in known_attribs:
1088 return collections[known_attribs[index]]._items[index].value
1089
1090
1091 return _object_getattribute(self, index)
1092
1093
1095 if index[0] == '_':
1096 return _object_setattr(self, index, value)
1097
1098
1099 s_dict = _object_getattribute(self, '__dict__')
1100 known_attribs = s_dict['_known_attribs']
1101 if index in known_attribs:
1102 collections = s_dict['_collections']
1103 collections[known_attribs[index]][index].value = value
1104 return value
1105
1106
1107 return _object_setattr(self, index, value)
1108
1109
1110
1112 for collection in self._collections.values():
1113 collection.reset()
1114
1115
1117 s = "%s:" % (self.__class__.__name__)
1118 if self.__descr is not None:
1119 s += "/%s " % self.__descr
1120 if hasattr(self, "_collections"):
1121 for col, collection in self._collections.iteritems():
1122 s += " %d %s:%s" % (len(collection.items), col, str(collection))
1123 return s
1124
1125
1126 - def __repr__(self, prefixes=None, fullname=False):
1127 """String definition of the object of ClassWithCollections object
1128
1129 :Parameters:
1130 fullname : bool
1131 Either to include full name of the module
1132 prefixes : list of strings
1133 What other prefixes to prepend to list of arguments
1134 """
1135 if prefixes is None:
1136 prefixes = []
1137 prefixes = prefixes[:]
1138 id_str = ""
1139 module_str = ""
1140 if __debug__:
1141 if 'MODULE_IN_REPR' in debug.active:
1142 fullname = True
1143 if 'ID_IN_REPR' in debug.active:
1144 id_str = '#%s' % id(self)
1145
1146 if fullname:
1147 modulename = '%s' % self.__class__.__module__
1148 if modulename != "__main__":
1149 module_str = "%s." % modulename
1150
1151
1152 collections = self._collections
1153
1154 for col in _COLLECTIONS_ORDER:
1155 collection = collections.get(col, None)
1156 if collection is None:
1157 continue
1158 prefixes += collection._cls_repr()
1159
1160
1161 descr = self.__descr
1162 if descr is not None:
1163 prefixes.append("descr=%s" % repr(descr))
1164
1165 return "%s%s(%s)%s" % (module_str, self.__class__.__name__,
1166 ', '.join(prefixes), id_str)
1167
1168
1169 descr = property(lambda self: self.__descr,
1170 doc="Description of the object if any")
1171
1172
1173
1175 """Classes inherited from this class intend to collect attributes
1176 within internal processing.
1177
1178 Subclassing Harvestable we gain ability to collect any internal
1179 data from the processing which is especially important if an
1180 object performs something in loop and discards some intermidiate
1181 possibly interesting results (like in case of
1182 CrossValidatedTransferError and states of the trained classifier
1183 or TransferError).
1184
1185 """
1186
1187 harvested = StateVariable(enabled=False, doc=
1188 """Store specified attributes of classifiers at each split""")
1189
1190 _KNOWN_COPY_METHODS = [ None, 'copy', 'deepcopy' ]
1191
1192
1193 - def __init__(self, harvest_attribs=None, copy_attribs='copy', **kwargs):
1194 """Initialize state of harvestable
1195
1196 :Parameters:
1197 harvest_attribs : list of basestr or dicts
1198 What attributes of call to store and return within
1199 harvested state variable. If an item is a dictionary,
1200 following keys are used ['name', 'copy']
1201 copy_attribs : None or basestr
1202 Default copying. If None -- no copying, 'copy'
1203 - shallow copying, 'deepcopy' -- deepcopying
1204
1205 """
1206 ClassWithCollections.__init__(self, **kwargs)
1207
1208 self.__atribs = harvest_attribs
1209 self.__copy_attribs = copy_attribs
1210
1211 self._setAttribs(harvest_attribs)
1212
1213
1215 """Set attributes to harvest
1216
1217 Each attribute in self.__attribs must have following fields
1218 - name : functional (or arbitrary if 'obj' or 'attr' is set)
1219 description of the thing to harvest,
1220 e.g. 'transerror.clf.training_time'
1221 - obj : name of the object to harvest from (if empty,
1222 'self' is assumed),
1223 e.g 'transerror'
1224 - attr : attribute of 'obj' to harvest,
1225 e.g. 'clf.training_time'
1226 - copy : None, 'copy' or 'deepcopy' - way to copy attribute
1227 """
1228 if attribs:
1229
1230 self.states.enable('harvested')
1231 self.__attribs = []
1232 for i, attrib in enumerate(attribs):
1233 if isinstance(attrib, dict):
1234 if not 'name' in attrib:
1235 raise ValueError, \
1236 "Harvestable: attribute must be a string or " + \
1237 "a dictionary with 'name'"
1238 else:
1239 attrib = {'name': attrib}
1240
1241
1242 if not 'copy' in attrib:
1243 attrib['copy'] = self.__copy_attribs
1244
1245
1246 if not attrib['copy'] in self._KNOWN_COPY_METHODS:
1247 raise ValueError, "Unknown method %s. Known are %s" % \
1248 (attrib['copy'], self._KNOWN_COPY_METHODS)
1249
1250 if not ('obj' in attrib or 'attr' in attrib):
1251
1252
1253 split = attrib['name'].split('.', 1)
1254 if len(split)==1:
1255 obj, attr = split[0], None
1256 else:
1257 obj, attr = split
1258 attrib.update({'obj':obj, 'attr':attr})
1259
1260 if attrib['obj'] == '':
1261 attrib['obj'] = 'self'
1262
1263
1264
1265 self.__attribs.append(attrib)
1266 else:
1267
1268 self.__attribs = []
1269
1270
1272 """The harvesting function: must obtain dictionary of variables
1273 from the caller.
1274
1275 :Parameters:
1276 vars : dict
1277 Dictionary of available data. Most often locals() could be
1278 passed as `vars`. Mention that desired to be harvested
1279 private attributes better be bound locally to some variable
1280
1281 :Returns:
1282 nothing
1283 """
1284
1285 if not self.states.isEnabled('harvested') or len(self.__attribs)==0:
1286 return
1287
1288 if not self.states.isSet('harvested'):
1289 self.harvested = dict([(a['name'], []) for a in self.__attribs])
1290
1291 for attrib in self.__attribs:
1292 attrv = vars[attrib['obj']]
1293
1294
1295 if not attrib['attr'] is None:
1296 attrv = eval('attrv.%s' % attrib['attr'])
1297
1298
1299 attrv = {'copy':copy.copy,
1300 'deepcopy':copy.deepcopy,
1301 None:lambda x:x}[attrib['copy']](attrv)
1302
1303 self.harvested[attrib['name']].append(attrv)
1304
1305
1306 harvest_attribs = property(fget=lambda self:self.__attribs,
1307 fset=_setAttribs)
1308