Search code examples
pythonpython-2.7decoratorpython-decoratorsmemoization

Pass Input Function to Multiple Class Decorators


I have a Timer decorator printing the time elapsed for a Memoize decorated function on screen. However, the decorator print statement prints the memoize class as the function name on screen rather than the function input to memoize. For instance, using this code:

from memoization import Memoize


import time


import logging


from timer import Timer


@Timer
@Memoize
def pass_and_square_time(seconds):
    time.sleep(seconds)
    return seconds**2


def main():
    logging.getLogger().setLevel(logging.ERROR)

    print '\nFor pass_and_square_time({30}):'.format(n=num)
    print '\n\tThe initial call of pass_and_square_time(30) yields: {ret}'.format(ret=pass_and_square_time(30))
    print '\n\tThe second call of pass_and_square_time(30) yields: {ret}'.format(ret=pass_and_square_time(30))

returns the following:

For pass_and_square_time(30):
    Timer Time Elapsed: 30.0 seconds

    <memoization.Memoize object at 0x02E5BBD0> 30.0 seconds

    The initial call of pass_and_square_time(30) yields: 900
    Timer Time Elapsed: 0.0 seconds

    <memoization.Memoize object at 0x02E5BBD0> 0.0 seconds

    The second call of pass_and_square_time(30) yields: 900

when I want that memoization.Memoize to be pass_and_square_time. I've tried a variety of different combinations of self.__wrapper__, functools.wraps, and functools.update_wrapper() to no avail.

My Timer class is implemented as follows:

class Timer(object):
    def __init__(self, fcn=None, timer_name='Timer'):
        self._start_time = None
        self._last_timer_result = None
        self._display = 'seconds'
        self._fcn = fcn
        self._timer_name = timer_name

    def __call__(self, *args):
        self.start()
        fcn_res = self._fcn(*args)
        self.end()
        print '\n\t{func} {time} seconds'.format(func=self._fcn, time=self.last_timer_result)
        return fcn_res

    def __get__(self, obj, objtype):
        return partial(self.__call__, obj)

    '''
    start(), end(), and last_timer_result functions/properties implemented 
    below in order to set the start_time, set the end_time and calculate the 
    last_timer_result,  and return the last_timer_result. I can include more
    if you need it. I didn't include it just because I didn't want to make
    the post too long
    '''

My Memoize class is implemented as follows:

from functools import update_wrapper, partial


class Memoize(object):
    def __init__(self, fcn):
        self._fcn = fcn
        self._memo = {}
        update_wrapper(self, fcn)

    def __call__(self, *args):
        if args not in self._memo:
            self._memo[args] = self._fcn(*args)

        return self._memo[args]

    def __get__(self, obj, objtype):
        return partial(self.__call__, obj)

Solution

  • This is a lot simpler using closures than classes.

    import functools
    import time
    
    def memoize(f):
        _memo = {}
        @functools.wraps(f)
        def _(*args):
            if args not in _memo:
                _memo[args] = f(*args)
            return _memo[args]
        return _                
    

    I would similarly write timer as

    def timer(f):
        @functools.wraps(f)
        def _(*args):
            start = time.time()
            rv = f(*args)
            end = time.time()
            print '\n\t{func} {t} seconds'.format(func=f.__name__, t=end - start)
            return rv
        return _
    

    Then

     @timer
     @memoize
     def pass_and_square_time(seconds):
         time.sleep(seconds)
         return seconds**2