Is there a pythonic way to support keyword arguments for a memoize decorator in Python?
所以我最近问了一个关于memoization的问题,得到了一些很好的答案,现在我想把它提升到一个新的水平。经过相当多的谷歌搜索后,我找不到memoize装饰器的参考实现,它能够缓存一个带有关键字参数的函数。事实上,他们中的大多数只是使用
在我的例子中,函数的第一个参数本身就是一个唯一的标识符,适合用作缓存查找的dict键,但我希望能够使用关键字参数并仍然访问相同的缓存。我的意思是,
为了做到这一点,我们需要的是一种清洁和pythonic的方式来说'检查kwargs是否与第一个参数相对应的关键字''。这就是我想出的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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] |
疯狂的是,这确实有效。例如,如果您这样装饰:
1 2 3 4 5 6 | @memoize class FooBar: instances = {} def __init__(self, unique_id, irrelevant=None): print id(self) |
然后从您的代码中,您可以调用
问题是,这是一个不敬虔的混乱;-)
它的工作原理是因为
如果这样的事情甚至存在,那么更好的是
1 2 3 4 5 | 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] |
我可以看到解决这个问题的另一种方法是直接使用
有没有人对此有任何想法?想要使用关键字参数调用函数并缓存结果是错误的吗?或者只是非常困难?
我建议如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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())) |
例:
1 2 3 4 5 6 7 8 | @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 |
请注意,您还可以扩展
1 2 3 4 5 6 7 8 9 10 11 | 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') |
试试lru_cache:
Decorator to wrap a function with a memoizing callable that saves up to the maxsize most recent calls. It can save time when an expensive or I/O bound function is periodically called with the same arguments.
lru_cache在python 3.2中添加,但可以反向移植到2.x.