3 Memoizer -- base class to provide automatic, optimized caching of
4 method return values for subclassed objects. Caching is activated by
5 the presence of "__cacheable__" in the doc of a method (acts like a
6 decorator). The presence of "__cache_reset__" or "__reset_cache__"
7 in the doc string instead indicates a method that should reset the
8 cache, discarding any currently cached values.
10 Note: current implementation is optimized for speed, not space. The
11 cache reset operation does not actually discard older results, and in
12 fact, all cached results (and keys) are held indefinitely.
14 Most of the work for this is done by copying and modifying the class
15 definition itself, rather than the object instances. This will
16 therefore allow all instances of a class to get caching activated
17 without requiring lengthy initialization or other management of the
20 [This could also be done using metaclassing (which would require
21 Python 2.2) and decorators (which would require Python 2.4). Current
22 implementation is used due to Python 1.5.2 compatability requirement
27 * All local methods/attributes use a prefix of "_MeMoIZeR" to avoid
28 namespace collisions with the attributes of the objects
31 * Based on performance evaluations of dictionaries, caching is
32 done by providing each object with a unique key attribute and
33 using the value of that attribute as an index for dictionary
34 lookup. If an object doesn't have one of these attributes,
35 fallbacks are utilized (although they will be somewhat slower).
37 * To support this unique-value attribute correctly, it must be
38 removed whenever a __cmp__ operation is performed, and it must
39 be updated whenever a copy.copy or copy.deepcopy is performed,
40 so appropriate manipulation is provided by the Caching code
43 * Cached values are stored in the class (indexed by the caching
44 key attribute, then by the name of the method called and the
45 constructed key of the arguments passed). By storing them here
46 rather than on the instance, the instance can be compared,
47 copied, and pickled much easier.
51 * The method by which caching is implemented can be changed in a
52 single location and it will apply globally.
54 * Greatly simplified client code: remove lots of try...except or
55 similar handling of cached lookup. Also usually more correct in
56 that it based caching on all input arguments whereas many
57 hand-implemented caching operations often miss arguments that
60 * Caching can be globally disabled very easily (for testing, etc.)
67 # Permission is hereby granted, free of charge, to any person obtaining
68 # a copy of this software and associated documentation files (the
69 # "Software"), to deal in the Software without restriction, including
70 # without limitation the rights to use, copy, modify, merge, publish,
71 # distribute, sublicense, and/or sell copies of the Software, and to
72 # permit persons to whom the Software is furnished to do so, subject to
73 # the following conditions:
75 # The above copyright notice and this permission notice shall be included
76 # in all copies or substantial portions of the Software.
78 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
79 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
80 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
81 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
82 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
83 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
84 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
87 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
89 #TBD: for pickling, should probably revert object to unclassed state...
96 # A flag controlling whether or not we actually use memoization.
100 # Generate a key for an object that is to be used as the caching key
103 # Current implementation: singleton generating a monotonically
108 self._next_keyval = 0
110 r = self._next_keyval
111 self._next_keyval = self._next_keyval + 1
113 Next_Memoize_Key = MemoizerKey()
117 # Memoized Class management.
119 # Classes can be manipulated just like object instances; we are going
120 # to do some of that here, without the benefit of metaclassing
121 # introduced in Python 2.2 (it would be nice to use that, but this
122 # attempts to maintain backward compatibility to Python 1.5.2).
124 # The basic implementation therefore is to update the class definition
125 # for any objects that we want to enable caching for. The updated
126 # definition performs caching activities for those methods
127 # appropriately marked in the original class.
129 # When an object is created, its class is switched to this updated,
130 # cache-enabled class definition, thereby enabling caching operations.
132 # To get an instance to used the updated, caching class, the instance
133 # must declare the Memoizer as a base class and make sure to call the
134 # Memoizer's __init__ during the instance's __init__. The Memoizer's
135 # __init__ will perform the class updating.
137 # For Python 2.2 and later, where metaclassing is supported, it is
138 # sufficient to provide a "__metaclass__ = Memoized_Metaclass" as part
139 # of the class definition; the metaclassing will automatically invoke
140 # the code herein properly.
143 ##def ALT0_MeMoIZeR_gen_key(argtuple, kwdict):
144 ## return cPickle.dumps( (argtuple,kwdict) )
146 def ALT1_MeMoIZeR_gen_key(argtuple, kwdict):
147 return repr(argtuple) + '|' + repr(kwdict)
149 def ALT2_MeMoIZeR_gen_key(argtuple, kwdict):
150 return string.join(map(lambda A:
151 getattr(A, '_MeMoIZeR_Key', str(A)),
155 getattr(D[1], '_MeMoIZeR_Key', str(D[1])),
159 def ALT3_MeMoIZeR_gen_key(argtuple, kwdict):
162 X = getattr(A, '_MeMoIZeR_Key', None)
167 for K,V in kwdict.items():
169 X = getattr(V, '_MeMoIZeR_Key', None)
174 return string.join(ret, '|')
176 def ALT4_MeMoIZeR_gen_key(argtuple, kwdict):
178 return string.join(map(lambda A:
179 getattr(A, '_MeMoIZeR_Key', None) or str(A),
183 (getattr(D[1], '_MeMoIZeR_Key', None) or str(D[1])),
186 return string.join(map(lambda A:
187 getattr(A, '_MeMoIZeR_Key', None) or str(A),
191 def ALT5_MeMoIZeR_gen_key(argtuple, kwdict):
192 A = string.join(map(str, argtuple), '|')
195 I = map(lambda K,D=kwdict: str(K)+'='+str(D[K]), kwdict.keys())
196 K = string.join(I, '|')
197 return string.join([A,K], '!')
199 def ALT6_MeMoIZeR_gen_key(argtuple, kwdict):
200 A = string.join(map(str, map(id, argtuple)), '|')
203 I = map(lambda K,D=kwdict: str(K)+'='+str(id(D[K])), kwdict.keys())
204 K = string.join(I, '|')
205 return string.join([A,K], '!')
207 def ALT7_MeMoIZeR_gen_key(argtuple, kwdict):
208 A = string.join(map(repr, argtuple), '|')
211 I = map(lambda K,D=kwdict: repr(K)+'='+repr(D[K]), kwdict.keys())
212 K = string.join(I, '|')
213 return string.join([A,K], '!')
215 def ALT8_MeMoIZeR_gen_key(argtuple, kwdict):
218 X = getattr(A, '_MeMoIZeR_Key', None)
223 for K,V in kwdict.items():
225 X = getattr(V, '_MeMoIZeR_Key', None)
230 return string.join(ret, '|')
232 def ALT9_MeMoIZeR_gen_key(argtuple, kwdict):
236 X = A.__dict__.get('_MeMoIZeR_Key', None) or repr(A)
237 except (AttributeError, KeyError):
240 for K,V in kwdict.items():
244 X = V.__dict__.get('_MeMoIZeR_Key', None) or repr(V)
245 except (AttributeError, KeyError):
248 return string.join(ret, '|')
250 #_MeMoIZeR_gen_key = ALT9_MeMoIZeR_gen_key # 8.8, 0.20
251 _MeMoIZeR_gen_key = ALT8_MeMoIZeR_gen_key # 8.5, 0.18
252 #_MeMoIZeR_gen_key = ALT7_MeMoIZeR_gen_key # 8.7, 0.17
253 #_MeMoIZeR_gen_key = ALT6_MeMoIZeR_gen_key #
254 #_MeMoIZeR_gen_key = ALT5_MeMoIZeR_gen_key # 9.7, 0.20
255 #_MeMoIZeR_gen_key = ALT4_MeMoIZeR_gen_key # 8.6, 0.19
256 #_MeMoIZeR_gen_key = ALT3_MeMoIZeR_gen_key # 8.5, 0.20
257 #_MeMoIZeR_gen_key = ALT2_MeMoIZeR_gen_key # 10.1, 0.22
258 #_MeMoIZeR_gen_key = ALT1_MeMoIZeR_gen_key # 8.6 0.18
262 ## This is really the core worker of the Memoize module. Any
263 ## __cacheable__ method ends up calling this function which tries to
264 ## return a previously cached value if it exists, and which calls the
265 ## actual function and caches the return value if it doesn't already
268 ## This function should be VERY efficient: it will get called a lot
269 ## and its job is to be faster than what would be called.
271 def Memoizer_cache_get(func, cdict, args, kw):
272 """Called instead of name to see if this method call's return
273 value has been cached. If it has, just return the cached
274 value; if not, call the actual method and cache the return."""
278 ckey = obj._MeMoIZeR_Key + ':' + _MeMoIZeR_gen_key(args, kw)
281 ## rval = cdict[ckey]
283 ## rval = cdict[ckey] = apply(func, args, kw)
285 rval = cdict.get(ckey, "_MeMoIZeR")
286 if rval is "_MeMoIZeR":
287 rval = cdict[ckey] = apply(func, args, kw)
289 ## rval = cdict.setdefault(ckey, apply(func, args, kw))
291 ## if cdict.has_key(ckey):
292 ## rval = cdict[ckey]
294 ## rval = cdict[ckey] = apply(func, args, kw)
298 def Memoizer_cache_get_self(func, cdict, self):
299 """Called instead of func(self) to see if this method call's
300 return value has been cached. If it has, just return the cached
301 value; if not, call the actual method and cache the return.
302 Optimized version of Memoizer_cache_get for methods that take the
303 object instance as the only argument."""
305 ckey = self._MeMoIZeR_Key
308 ## rval = cdict[ckey]
310 ## rval = cdict[ckey] = func(self)
312 rval = cdict.get(ckey, "_MeMoIZeR")
313 if rval is "_MeMoIZeR":
314 rval = cdict[ckey] = func(self)
316 ## rval = cdict.setdefault(ckey, func(self)))
318 ## if cdict.has_key(ckey):
319 ## rval = cdict[ckey]
321 ## rval = cdict[ckey] = func(self)
325 def Memoizer_cache_get_one(func, cdict, self, arg):
326 """Called instead of func(self, arg) to see if this method call's
327 return value has been cached. If it has, just return the cached
328 value; if not, call the actual method and cache the return.
329 Optimized version of Memoizer_cache_get for methods that take the
330 object instance and one other argument only."""
332 ## X = getattr(arg, "_MeMoIZeR_Key", None)
334 ## ckey = self._MeMoIZeR_Key +':'+ X
336 ## ckey = self._MeMoIZeR_Key +':'+ str(arg)
337 ckey = self._MeMoIZeR_Key + ':' + \
338 (getattr(arg, "_MeMoIZeR_Key", None) or repr(arg))
341 ## rval = cdict[ckey]
343 ## rval = cdict[ckey] = func(self, arg)
345 rval = cdict.get(ckey, "_MeMoIZeR")
346 if rval is "_MeMoIZeR":
347 rval = cdict[ckey] = func(self, arg)
349 ## rval = cdict.setdefault(ckey, func(self, arg)))
351 ## if cdict.has_key(ckey):
352 ## rval = cdict[ckey]
354 ## rval = cdict[ckey] = func(self, arg)
359 # Caching stuff is tricky, because the tradeoffs involved are often so
360 # non-obvious, so we're going to support an alternate set of functions
361 # that also count the hits and misses, to try to get a concrete idea of
362 # which Memoizations seem to pay off.
364 # Because different configurations can have such radically different
365 # performance tradeoffs, interpreting the hit/miss results will likely be
366 # more of an art than a science. In other words, don't assume that just
367 # because you see no hits in one configuration that it's not worthwhile
368 # Memoizing that method.
370 # Note that these are essentially cut-and-paste copies of the above
371 # Memozer_cache_get*() implementations, with the addition of the
372 # counting logic. If the above implementations change, the
373 # corresponding change should probably be made down below as well,
374 # just to try to keep things in sync.
383 class Counter(UserDict.UserDict):
384 def __call__(self, obj, methname):
385 k = obj.__class__.__name__ + '.' + methname
389 c = self[k] = CounterEntry()
392 CacheCount = Counter()
393 CacheCountSelf = Counter()
394 CacheCountOne = Counter()
397 items = CacheCount.items() + CacheCountSelf.items() + CacheCountOne.items()
400 print " %7d hits %7d misses %s()" % (v.hit, v.miss, k)
402 def Count_cache_get(name, func, cdict, args, kw):
403 """Called instead of name to see if this method call's return
404 value has been cached. If it has, just return the cached
405 value; if not, call the actual method and cache the return."""
409 ckey = obj._MeMoIZeR_Key + ':' + _MeMoIZeR_gen_key(args, kw)
411 c = CacheCount(obj, name)
412 rval = cdict.get(ckey, "_MeMoIZeR")
413 if rval is "_MeMoIZeR":
414 rval = cdict[ckey] = apply(func, args, kw)
421 def Count_cache_get_self(name, func, cdict, self):
422 """Called instead of func(self) to see if this method call's
423 return value has been cached. If it has, just return the cached
424 value; if not, call the actual method and cache the return.
425 Optimized version of Memoizer_cache_get for methods that take the
426 object instance as the only argument."""
428 ckey = self._MeMoIZeR_Key
430 c = CacheCountSelf(self, name)
431 rval = cdict.get(ckey, "_MeMoIZeR")
432 if rval is "_MeMoIZeR":
433 rval = cdict[ckey] = func(self)
440 def Count_cache_get_one(name, func, cdict, self, arg):
441 """Called instead of func(self, arg) to see if this method call's
442 return value has been cached. If it has, just return the cached
443 value; if not, call the actual method and cache the return.
444 Optimized version of Memoizer_cache_get for methods that take the
445 object instance and one other argument only."""
447 ckey = self._MeMoIZeR_Key + ':' + \
448 (getattr(arg, "_MeMoIZeR_Key", None) or repr(arg))
450 c = CacheCountOne(self, name)
451 rval = cdict.get(ckey, "_MeMoIZeR")
452 if rval is "_MeMoIZeR":
453 rval = cdict[ckey] = func(self, arg)
461 'MCG' : Memoizer_cache_get,
462 'MCGS' : Memoizer_cache_get_self,
463 'MCGO' : Memoizer_cache_get_one,
466 MCG_lambda = "lambda *args, **kw: MCG(methcode, methcached, args, kw)"
467 MCGS_lambda = "lambda self: MCGS(methcode, methcached, self)"
468 MCGO_lambda = "lambda self, arg: MCGO(methcode, methcached, self, arg)"
470 def EnableCounting():
471 """Enable counting of Memoizer hits and misses by overriding the
472 globals that hold the non-counting versions of the functions and
473 lambdas we call with the counting versions.
481 'MCG' : Count_cache_get,
482 'MCGS' : Count_cache_get_self,
483 'MCGO' : Count_cache_get_one,
486 MCG_lambda = "lambda *args, **kw: MCG(methname, methcode, methcached, args, kw)"
487 MCGS_lambda = "lambda self: MCGS(methname, methcode, methcached, self)"
488 MCGO_lambda = "lambda self, arg: MCGO(methname, methcode, methcached, self, arg)"
492 class _Memoizer_Simple:
494 def __setstate__(self, state):
495 self.__dict__.update(state)
496 self.__dict__['_MeMoIZeR_Key'] = Next_Memoize_Key()
497 #kwq: need to call original's setstate if it had one...
499 def _MeMoIZeR_reset(self):
500 self.__dict__['_MeMoIZeR_Key'] = Next_Memoize_Key()
504 class _Memoizer_Comparable:
506 def __setstate__(self, state):
507 self.__dict__.update(state)
508 self.__dict__['_MeMoIZeR_Key'] = Next_Memoize_Key()
509 #kwq: need to call original's setstate if it had one...
511 def _MeMoIZeR_reset(self):
512 self.__dict__['_MeMoIZeR_Key'] = Next_Memoize_Key()
515 def __cmp__(self, other):
516 """A comparison might use the object dictionaries to
517 compare, so the dictionaries should contain caching
518 entries. Make new dictionaries without those entries
519 to use with the underlying comparison."""
524 # We are here as a cached object, but cmp will flip its
525 # arguments back and forth and recurse attempting to get base
526 # arguments for the comparison, so we might have already been
530 saved_d1 = self.__dict__
531 d1 = copy.copy(saved_d1)
532 del d1['_MeMoIZeR_Key']
534 return self._MeMoIZeR_cmp(other)
537 # Same thing for the other, but we should try to convert it
538 # here in case the _MeMoIZeR_cmp compares __dict__ objects
543 if other.__dict__.has_key('_MeMoIZeR_Key'):
544 saved_other = other.__dict__
545 d2 = copy.copy(saved_other)
546 del d2['_MeMoIZeR_Key']
548 except (AttributeError, KeyError):
551 # Both self and other have been prepared: perform the test,
552 # then restore the original dictionaries and exit
554 rval = self._MeMoIZeR_cmp(other)
556 self.__dict__ = saved_d1
558 other.__dict__ = saved_other
563 def Analyze_Class(klass):
564 if klass.__dict__.has_key('_MeMoIZeR_converted'): return klass
566 original_name = str(klass)
568 D,R,C = _analyze_classmethods(klass.__dict__, klass.__bases__)
571 modelklass = _Memoizer_Comparable
572 lcldict = {'_MeMoIZeR_cmp':C}
574 modelklass = _Memoizer_Simple
577 klass.__dict__.update(memoize_classdict(klass, modelklass, lcldict, D, R))
582 # Note that each eval("lambda...") has a few \n's prepended to the
583 # lambda, and furthermore that each of these evals has a different
584 # number of \n's prepended. This is to provide a little bit of info
585 # for traceback or profile output, which generate things like 'File
586 # "<string>", line X'. X will be the number of \n's plus 1.
588 # Also use the following routine to specify the "filename" portion so
589 # that it provides useful information. In addition, make sure it
590 # contains 'os.sep + "SCons" + os.sep' for the
591 # SCons.Script.find_deepest_user_frame operation.
593 def whoami(memoizer_funcname, real_funcname):
594 return '...'+os.sep+'SCons'+os.sep+'Memoizer-'+ \
595 memoizer_funcname+'-lambda<'+real_funcname+'>'
597 def memoize_classdict(klass, modelklass, new_klassdict, cacheable, resetting):
598 new_klassdict.update(modelklass.__dict__)
599 new_klassdict['_MeMoIZeR_converted'] = 1
601 for name,code in cacheable.items():
607 eval_dict.update(MCG_dict)
609 if fc.co_argcount == 1 and not fc.co_flags & 0xC:
610 compiled = compile("\n"*1 + MCGS_lambda,
611 whoami('cache_get_self', name),
613 elif fc.co_argcount == 2 and not fc.co_flags & 0xC:
614 compiled = compile("\n"*2 + MCGO_lambda,
615 whoami('cache_get_one', name),
618 compiled = compile("\n"*3 + MCG_lambda,
619 whoami('cache_get', name),
621 newmethod = eval(compiled, eval_dict, {})
622 new_klassdict[name] = newmethod
624 for name,code in resetting.items():
627 "lambda obj_self, *args, **kw: (obj_self._MeMoIZeR_reset(), apply(rmethcode, (obj_self,)+args, kw))[1]",
628 whoami('cache_reset', name),
630 {'rmethcode':code}, {})
631 new_klassdict[name] = newmethod
635 def _analyze_classmethods(klassdict, klassbases):
636 """Given a class, performs a scan of methods for that class and
637 all its base classes (recursively). Returns aggregated results of
638 _scan_classdict calls where subclass methods are superimposed over
639 base class methods of the same name (emulating instance->class
646 # Get cache/reset/cmp methods from subclasses
649 if K.__dict__.has_key('_MeMoIZeR_converted'): continue
650 d,r,c = _analyze_classmethods(K.__dict__, K.__bases__)
655 # Delete base method info if current class has an override
658 if M == '__cmp__': continue
659 if klassdict.has_key(M):
662 if M == '__cmp__': continue
663 if klassdict.has_key(M):
666 # Get cache/reset/cmp from current class
668 d,r,c = _scan_classdict(klassdict)
670 # Update accumulated cache/reset/cmp methods
679 def _scan_classdict(klassdict):
680 """Scans the method dictionary of a class to find all methods
681 interesting to caching operations. Returns a tuple of these
684 ( dict-of-cachable-methods,
685 dict-of-cache-resetting-methods,
686 cmp_method_val or None)
688 Each dict has the name of the method as a key and the corresponding
689 value is the method body."""
694 already_cache_modified = 0
696 for attr,val in klassdict.items():
697 if not callable(val): continue
698 if attr == '__cmp__':
700 continue # cmp can't be cached and can't reset cache
701 if attr == '_MeMoIZeR_cmp':
702 already_cache_modified = 1
704 if not val.__doc__: continue
705 if string.find(val.__doc__, '__cache_reset__') > -1:
706 cache_resetters[attr] = val
708 if string.find(val.__doc__, '__reset_cache__') > -1:
709 cache_resetters[attr] = val
711 if string.find(val.__doc__, '__cacheable__') > -1:
712 cache_setters[attr] = val
714 if already_cache_modified: cmp_if_exists = 'already_cache_modified'
715 return cache_setters, cache_resetters, cmp_if_exists
718 # Primary Memoizer class. This should be a base-class for any class
719 # that wants method call results to be cached. The sub-class should
720 # call this parent class's __init__ method, but no other requirements
721 # are made on the subclass (other than appropriate decoration).
724 """Object which performs caching of method calls for its 'primary'
728 self.__class__ = Analyze_Class(self.__class__)
729 self._MeMoIZeR_Key = Next_Memoize_Key()
731 # Find out if we are pre-2.2
734 vinfo = sys.version_info
735 except AttributeError:
736 """Split an old-style version string into major and minor parts. This
737 is complicated by the fact that a version string can be something
740 version = string.split(string.split(sys.version, ' ')[0], '.')
741 vinfo = (int(version[0]), int(re.match('\d+', version[1]).group()))
744 need_version = (2, 2) # actual
745 #need_version = (33, 0) # always
746 #need_version = (0, 0) # never
748 has_metaclass = (vinfo[0] > need_version[0] or \
749 (vinfo[0] == need_version[0] and
750 vinfo[1] >= need_version[1]))
752 if not has_metaclass:
754 class Memoized_Metaclass:
755 # Just a place-holder so pre-metaclass Python versions don't
756 # have to have special code for the Memoized classes.
761 # Initialization is a wee bit of a hassle. We want to do some of
762 # our own work for initialization, then pass on to the actual
763 # initialization function. However, we have to be careful we
764 # don't interfere with (a) the super()'s initialization call of
765 # it's superclass's __init__, and (b) classes we are Memoizing
766 # that don't have their own __init__ but which have a super that
767 # has an __init__. To do (a), we eval a lambda below where the
768 # actual init code is locally bound and the __init__ entry in the
769 # class's dictionary is replaced with the _MeMoIZeR_init call. To
770 # do (b), we use _MeMoIZeR_superinit as a fallback if the class
771 # doesn't have it's own __init__. Note that we don't use getattr
772 # to obtain the __init__ because we don't want to re-instrument
773 # parent-class __init__ operations (and we want to avoid the
774 # Object object's slot init if the class has no __init__).
776 def _MeMoIZeR_init(actual_init, self, args, kw):
777 self.__dict__['_MeMoIZeR_Key'] = Next_Memoize_Key()
778 apply(actual_init, (self,)+args, kw)
780 def _MeMoIZeR_superinit(self, cls, args, kw):
781 apply(super(cls, self).__init__, args, kw)
783 class Memoized_Metaclass(type):
784 def __init__(cls, name, bases, cls_dict):
785 # Note that cls_dict apparently contains a *copy* of the
786 # attribute dictionary of the class; modifying cls_dict
787 # has no effect on the actual class itself.
788 D,R,C = _analyze_classmethods(cls_dict, bases)
790 modelklass = _Memoizer_Comparable
791 cls_dict['_MeMoIZeR_cmp'] = C
793 modelklass = _Memoizer_Simple
794 klassdict = memoize_classdict(cls, modelklass, cls_dict, D, R)
796 init = klassdict.get('__init__', None)
798 # Make sure filename has os.sep+'SCons'+os.sep so that
799 # SCons.Script.find_deepest_user_frame doesn't stop here
800 import inspect # It's OK, can't get here for Python < 2.1
801 filename = inspect.getsourcefile(_MeMoIZeR_superinit)
803 # This file was compiled at a path name different from
804 # how it's invoked now, so just make up something.
805 filename = whoami('superinit', '???')
806 superinitcode = compile(
807 "lambda self, *args, **kw: MPI(self, cls, args, kw)",
810 superinit = eval(superinitcode,
812 'MPI':_MeMoIZeR_superinit})
815 newinitcode = compile(
816 "\n"*(init.func_code.co_firstlineno-1) +
817 "lambda self, args, kw: _MeMoIZeR_init(real_init, self, args, kw)",
818 whoami('init', init.func_code.co_filename),
820 newinit = eval(newinitcode,
822 '_MeMoIZeR_init':_MeMoIZeR_init},
824 klassdict['__init__'] = lambda self, *args, **kw: newinit(self, args, kw)
826 super(Memoized_Metaclass, cls).__init__(name, bases, klassdict)
827 # Now, since klassdict doesn't seem to have affected the class
828 # definition itself, apply klassdict.
829 for attr in klassdict.keys():
830 setattr(cls, attr, klassdict[attr])
832 def DisableMemoization():
836 def use_old_memoization():
837 return use_memoizer and not has_metaclass