Search code examples
python-2.7decoratorpython-decoratorsmemoization

Python 2.7 - Using Classes as Decorators for Timing and Memoization


I'm completely new to Python and I'm currently working on a project where I have a Timer and Memoize class, both of which should have the ability to be used as decorators and can work with functions with any number of parameters.

Problem

My current problem is that I am trying to use both of them as decorators on a function; however, the Timer is only called on the first call of the function and not the second. For instance, using this code:

# Import the Memoize class from the memoization module
from memoization import Memoize


# Import the time module
import time


# Import the logging module
import logging


# Import the Timer class from the timer module
from timer import Timer


@Memoize
@Timer
def pass_and_square_time(seconds):
    # Call time.sleep(seconds)
    time.sleep(seconds)
    # Return the square of the input seconds amount.
    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):

<function pass_and_square_time at 0x02B9A870> 30.003000021 seconds

    The initial call of pass_and_square_time(30) yields: 900

    The second call of pass_and_square_time(30) yields: 900

when I want it to return the seconds above that second call as well (Since that'd be the time of the second. The time above the initial call is the initial call's time). I believe that the @Memoize decorator is working properly on that second call since it shows up pretty much initially after the first rather than performing the time.sleep(30) call.


Timer

My Timer class is implemented as follows:

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

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

    '''
    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
    '''

Memoize

My Memoize class is implemented as follows:

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

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

    return self._memo[args]

References Used

The references I looked at and tried to model my classes off or are:

Python Class Decorators

Python Memoization


Thank You for Reading and for Any Help You Can Provide!


Solution

  • With your code, you are memoizing a timed function, which means the timing code is also skipped when the arguments are found in the cache. If you reverse the order of the decorators

    @Timer
    @Memoize
    def pass_and_square_time(seconds):
        # Call time.sleep(seconds)
        time.sleep(seconds)
        # Return the square of the input seconds amount.
        return seconds**2
    

    now you are timing a memoized function. Whether or not the arguments are found in the cache, you time the call to the memoized function.