0

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!

strwars
  • 15
  • 7
  • Memoize is doing what it is supposed to be doing: it's returning the cached value of the last call with the given arguments, instead of calling the (timed) function again. – chepner Aug 17 '18 at 20:35
  • But is there a way in which I can get the timer to be called whether the memoize returns the cached value or if the function is called @chepner ? Basically I want to show that the cached values from memoize allow the pass_and_square_time() function to return the value faster on the second call using the Timer decoration if that makes sense. – strwars Aug 17 '18 at 20:40
  • Then you need to time the memoized function explicitly, rather than memoizing a timed function. (Which might be a simple as swapping the order of the two decorators; I haven't tested that.) – chepner Aug 17 '18 at 20:52
  • Swapping the order of the two decorators worked!! I thought I had tried that earlier to no avail, but I suppose one of my later edits to the code could've fixed it. Thanks for the help on the silly question @chepner – strwars Aug 17 '18 at 21:07

1 Answers1

0

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.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • The one issue with swapping the decorators is that the Timer prints the Memorize function name in it's time elapsed print statement rather than the pass_and_square_time function name. Any recommendations on how to resolve that @chepner ? Thanks again! – strwars Aug 17 '18 at 21:33
  • Look at the `functools` module; there's a decorator you can use to preserve the original function's metadata (`wraps`? `wrapped`?) – chepner Aug 17 '18 at 21:45
  • I tried wraps, but I think I need to be returning a wrapped function within the memoize function for it to work since it is a decorator @chepner . – strwars Aug 18 '18 at 01:06