lonelyenvoy / python-memoization

A powerful caching library for Python, with TTL support and multiple algorithm options.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Memoization of classes does not work

dzimmanck opened this issue · comments

I am attempting to memoize a class that has an expensive calculation in the init. If the class has a str method, decorating the class with a @cached() does not throw an error, but also does not seem to function (i.e. No memoization observed). If you use a custom key maker, you get an TypeError saying the key maker signature does not match the function arguments (Think that is a clue right there).

Memoizing classes is not that uncommon of a feature so I think this should be supported. Here is a simple version if the problem below for reproducing.

from memoization import cached
import time
import timeit

class Point():
    def __init__(self, x, y):
	    self.x = x
	    self.y = y

	    # some super intense computation here
	    time.sleep(5)

# custom key-maker for the class
def key_maker(point):
    key = hash((point.x, point.y))
    return key

@cached(custom_key_maker=key_maker)
class CachedPoint(Point):
    def __init__(self, x, y):
        super().__init__(x, y)


if __name__ == "__main__":

    t0 = timeit.default_timer()
    p = Point(1, 2)
    t1 = timeit.default_timer()
    print(f'Making a point took {t1-t0} seconds')


t0 = timeit.default_timer()
p = Point(1, 2)
t1 = timeit.default_timer()
print(f'Re-making a point took {t1-t0} seconds')

You are not using @cached correctly.

  • @cached is applied to CachedPoint, so only calls to CachedPoint() will be cached, and calls to Point() will not be cached.
  • The custom_key_maker should be a function with the same signature as the cached function. Caching a class is indeed caching its __init__ magic method. In your case, custom_key_maker should have the same arguments as CachedPoint.__init__, for example:
def key_maker(x, y):
    return f'x={x},y={y}'

Note that in your code, a custom key maker is not necessary. memoization deals with built-in types like int/str automatically using the default key maker.

So, the following code works:

from memoization import cached
import time
import timeit

class Point():
    def __init__(self, x, y):
        self.x = x
        self.y = y

        # some super intense computation here
        time.sleep(5)

@cached
class CachedPoint(Point):
    def __init__(self, x, y):
        super().__init__(x, y)

if __name__ == "__main__":
    t0 = timeit.default_timer()
    p = CachedPoint(1, 2)
    t1 = timeit.default_timer()
    print(f'Making a point took {t1-t0} seconds')

    t0 = timeit.default_timer()
    p = CachedPoint(1, 2)
    t1 = timeit.default_timer()
    print(f'Re-making a point took {t1-t0} seconds')

Output:

Making a point took 5.0022731049999996 seconds
Re-making a point took 1.9499999999617046e-05 seconds

If you have further questions, please leave a comment and reopen this issue. Thanks!