youknowone / methodtools

Expand functools features(lru_cache) to class - methods, classmethods, staticmethods and even for (unofficial) hybrid methods.

Home Page:https://pypi.org/project/methodtools/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Unable to use lru_cache not as decorator

LukeMondy opened this issue · comments

Hello,

I'm having a little trouble trying to use the lru_cache not as a decorator.

So my goal is to allow a user to be allowed to supply a function to class, and still be cached. The functools.lru_cache allows this behavior, but methodtools comes up with an issue.

I've got some demo code here:

# A.py
from methodtools import lru_cache

class A(object):

    def __init__(self, alternate_function = None):
        if alternate_function:
            self.func_to_use = alternate_function
        else:
            self.func_to_use = self.cached_method

    @lru_cache(maxsize=100)
    def cached_method(self, args):
        return args

    def use_method(self, args):
        results = self.func_to_use(args)
        print(results)

and

# B.py
from methodtools import lru_cache

class B(object):

    def __init__(self, alternate_function = None):
        if alternate_function:
            self.func_to_use = alternate_function
        else:
            self.func_to_use = self.cached_method

        # We apply the cache here, not as a decorator
        self.func_to_use = lru_cache(maxsize=100)(self.func_to_use)

    def cached_method(self, args):
        return args

    def use_method(self, args):
        results = self.func_to_use(args)
        print(results)

which is then called via:

# test.py
from A import A
from B import B

# Use the default function, which has an lru_cache decorator
objA = A()
objA.use_method("1")
objA.use_method("2")
print(objA.func_to_use.cache_info())

# define an alternative function
def alt(args):
    return "woof: " + args

# Now the alternative function is called, but is not cached
objWoof = A(alternate_function = alt)
objWoof.use_method("1")
objWoof.use_method("2")

# Now we try to use class B, which tries to use the lru_cache not as a decorator
objB = B()
objB.use_method("1")
objB.use_method("2")
print(objB.func_to_use.cache_info())

def alt_meow(args):
    return "meow: " + args

# Now the alternative function is not cached
objBWoof = B(alternate_function = alt_meow)
objBWoof.use_method("1")
objBWoof.use_method("2")

If you run all that, you get:

python test.py
1
2
CacheInfo(hits=0, misses=2, maxsize=100, currsize=2)
woof: 1
woof: 2
Traceback (most recent call last):
  File "test.py", line 20, in <module>
    objB = B()
  File "/live/share/B.py", line 12, in __init__
    self.func_to_use = lru_cache(maxsize=100)(self.func_to_use)
  File "/usr/local/lib/python3.5/dist-packages/wirerope/rope.py", line 121, in __call__
    cw = Callable(function)
  File "/usr/local/lib/python3.5/dist-packages/wirerope/callable.py", line 108, in __init__
    f = getattr(f, self.descriptor.detect_function_attr_name())
  File "/usr/local/lib/python3.5/dist-packages/wirerope/callable.py", line 49, in detect_function_attr_name
    descriptor = self.descriptor_class(indicator)
TypeError: method expected 2 arguments, got 1

If you swap methodtools for functools in B.py, then the code works as expected. The trouble is that the functools way runs into pickling issues.

Are you able to provide any insight?

Thank you for reporting interesting use case. The hacky part of method detection is suspicious. Let me look into it.

Here is a new release with the fix: https://pypi.org/project/methodtools/0.4.0/

Great, thanks so much!