python - 有没有一种 pythonic 方法来支持 Python 中的 memoize 装饰器的关键字参数?

标签 python caching decorator memoization

所以我最近问了一个关于记忆化的问题并得到了一些很好的答案,现在我想把它提升到一个新的水平。经过相当多的谷歌搜索后,我找不到能够缓存采用关键字参数的函数的 memoize 装饰器的引用实现。事实上,他们中的大多数人只是简单地使用了*args。作为缓存查找的键,这意味着如果你想记住一个接受列表或字典作为参数的函数,它也会中断。

在我的例子中,函数的第一个参数本身就是一个唯一标识符,适合用作缓存查找的字典键,但是我希望能够使用关键字参数并仍然访问相同的缓存。我的意思是,my_func('unique_id', 10)my_func(foo=10, func_id='unique_id')应该都返回相同的缓存结果。

为了做到这一点,我们需要一种干净的 pythonic 方式来表达“检查 kwargs 中与第一个参数对应的任何关键字”)。这是我想出的:

class memoize(object):
    def __init__(self, cls):
        if type(cls) is FunctionType:
            # Let's just pretend that the function you gave us is a class.
            cls.instances = {}
            cls.__init__ = cls
        self.cls = cls
        self.__dict__.update(cls.__dict__)

    def __call__(self, *args, **kwargs):
        """Return a cached instance of the appropriate class if it exists."""
        # This is some dark magic we're using here, but it's how we discover
        # that the first argument to Photograph.__init__ is 'filename', but the
        # first argument to Camera.__init__ is 'camera_id' in a general way.
        delta = 2 if type(self.cls) is FunctionType else 1
        first_keyword_arg = [k
            for k, v in inspect.getcallargs(
                self.cls.__init__,
                'self',
                'first argument',
                *['subsequent args'] * (len(args) + len(kwargs) - delta)).items()
                    if v == 'first argument'][0]
        key = kwargs.get(first_keyword_arg) or args[0]
        print key
        if key not in self.cls.instances:
            self.cls.instances[key] = self.cls(*args, **kwargs)
        return self.cls.instances[key]

疯狂的是这确实有效。例如,如果你这样装饰:

@memoize
class FooBar:
    instances = {}

    def __init__(self, unique_id, irrelevant=None):
        print id(self)

然后从您的代码中您可以调用 FooBar('12345', 20)FooBar(irrelevant=20, unique_id='12345')并且实际上得到了相同的 FooBar 实例。然后,您可以为第一个参数定义一个具有不同名称的不同类,因为它以通用方式工作(即,装饰器不需要知道任何关于它正在装饰的类的具体信息才能使其工作)。

问题是,这是一团糟;-)

之所以有效,是因为 inspect.getcallargs返回一个将定义的关键字映射到您提供的参数的字典,因此我提供了一些虚假参数,然后检查字典中是否有第一个传递的参数。

如果这样的东西真的存在,那就更好了,类似于inspect.getcallargs返回的两种参数统一为参数列表,而不是关键字参数的字典。这将允许这样的事情:

def __call__(self, *args, **kwargs):
    key = inspect.getcallargsaslist(self.cls.__init__, None, *args, **kwargs)[1]
    if key not in self.cls.instances:
        self.cls.instances[key] = self.cls(*args, **kwargs)
    return self.cls.instances[key]

我认为解决这个问题的另一种方法是使用 inspect.getcallargs 提供的字典直接作为查找缓存键,但这需要一种可重复的方法来从相同的哈希中生成相同的字符串,这是我听说不能依赖的东西(我想我必须在排序后自己构造字符串键)。

有没有人对此有任何想法?想要调用带有关键字参数的函数并缓存结果是错误的吗?或者只是非常困难?

最佳答案

我建议如下:

import inspect

class key_memoized(object):
    def __init__(self, func):
       self.func = func
       self.cache = {}

    def __call__(self, *args, **kwargs):
        key = self.key(args, kwargs)
        if key not in self.cache:
            self.cache[key] = self.func(*args, **kwargs)
        return self.cache[key]

    def normalize_args(self, args, kwargs):
        spec = inspect.getargs(self.func.__code__).args
        return dict(kwargs.items() + zip(spec, args))

    def key(self, args, kwargs):
        a = self.normalize_args(args, kwargs)
        return tuple(sorted(a.items()))

示例:

@key_memoized
def foo(bar, baz, spam):
    print 'calling foo: bar=%r baz=%r spam=%r' % (bar, baz, spam)
    return bar + baz + spam

print foo(1, 2, 3)
print foo(1, 2, spam=3)         #memoized
print foo(spam=3, baz=2, bar=1) #memoized

请注意,您还可以扩展 key_memoized 并覆盖其 key() 方法以提供更具体的内存策略,例如忽略一些参数:

class memoize_by_bar(key_memoized):
    def key(self, args, kwargs):
        return self.normalize_args(args, kwargs)['bar']

@memoize_by_bar
def foo(bar, baz, spam):
    print 'calling foo: bar=%r baz=%r spam=%r' % (bar, baz, spam)
    return bar

print foo('x', 'ignore1', 'ignore2')
print foo('x', 'ignore3', 'ignore4')

关于python - 有没有一种 pythonic 方法来支持 Python 中的 memoize 装饰器的关键字参数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10920180/

相关文章:

css - Safari CSS - 使自定义样式表的版本无效

python - 从装饰器访问 django session

python - 乘积的有效双和

python - 有什么优雅的方法可以在 python 中构建多级字典吗?

caching - Safari 7 应用程序缓存不起作用

caching - 清除 GitHub 操作中的缓存

python - 以编程方式确定 Bin 路径

python - 在服务器上创建和维护多个 Python 脚本的最佳实践

dependency-injection - Angular 2 - 将依赖项注入(inject)装饰器工厂

Angular 4装饰器无法访问类范围