最近,StackOverflow 社区帮助我开发了一个相当简洁的@memoize
装饰器,它不仅能够以一般的方式装饰函数,还能够装饰方法和类,也就是说,无需预先知道它将装饰什么类型的东西。
我遇到的一个问题是,如果您使用 装饰一个类@memoize
,然后尝试使用 装饰其中一个方法@staticmethod
,这将无法按预期工作,即您根本无法调用ClassName.thestaticmethod()
。我想出的原始解决方案如下所示:
def memoize(obj):
"""General-purpose cache for classes, methods, and functions."""
cache = obj.cache = {}
def memoizer(*args, **kwargs):
"""Do cache lookups and populate the cache in the case of misses."""
key = args[0] if len(args) is 1 else args
if key not in cache:
cache[key] = obj(*args, **kwargs)
return cache[key]
# Make the memoizer func masquerade as the object we are memoizing.
# This makes class attributes and static methods behave as expected.
for k, v in obj.__dict__.items():
memoizer.__dict__[k] = v.__func__ if type(v) is staticmethod else v
return memoizer
但后来我了解到functools.wraps
,它的目的是让装饰器函数伪装成被装饰函数,以更干净、更完整的方式,实际上我是这样采用的:
def memoize(obj):
"""General-purpose cache for class instantiations, methods, and functions."""
cache = obj.cache = {}
@functools.wraps(obj)
def memoizer(*args, **kwargs):
"""Do cache lookups and populate the cache in the case of misses."""
key = args[0] if len(args) is 1 else args
if key not in cache:
cache[key] = obj(*args, **kwargs)
return cache[key]
return memoizer
虽然这看起来很不错,但functools.wraps
绝对不支持staticmethod
s 或classmethod
s。例如,如果你尝试过这样的事情:
@memoize
class Flub:
def __init__(self, foo):
"""It is an error to have more than one instance per foo."""
self.foo = foo
@staticmethod
def do_for_all():
"""Have some effect on all instances of Flub."""
for flub in Flub.cache.values():
print flub.foo
Flub('alpha') is Flub('alpha') #=> True
Flub('beta') is Flub('beta') #=> True
Flub.do_for_all() #=> 'alpha'
# 'beta'
这将适用于我列出的第一个实现@memoize
,但会TypeError: 'staticmethod' object is not callable
在第二个实现中引发。
我真的真的很想解决这个问题functools.wraps
而不必带回那种__dict__
丑陋,所以我实际上用纯 Python 重新实现了我自己staticmethod
的,它看起来像这样:
class staticmethod(object):
"""Make @staticmethods play nice with @memoize."""
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
"""Provide the expected behavior inside memoized classes."""
return self.func(*args, **kwargs)
def __get__(self, obj, objtype=None):
"""Re-implement the standard behavior for non-memoized classes."""
return self.func
据我所知,@memoize
这与我上面列出的第二个实现完美结合。
所以,我的问题是:为什么标准内置函数本身不能staticmethod
正常运行,和/或为什么不functools.wraps
预测这种情况并为我解决它?
这是 Python 中的错误吗?还是在functools.wraps
?
覆盖内置的注意事项是什么staticmethod
?就像我说的,它现在似乎工作正常,但我担心我的实现和内置实现之间可能存在一些隐藏的不兼容,这可能会在以后爆发。
谢谢。
编辑澄清:在我的应用程序中,我有一个函数进行昂贵的查找,并且经常被调用,所以我记住了它。这很简单。除此之外,我有许多代表文件的类,并且在文件系统中有多个代表同一个文件的实例通常会导致状态不一致,因此每个文件名只强制一个实例很重要。@memoize
使装饰器适应此目的并仍然保留其作为传统记忆器的功能本质上是微不足道的。
三种不同用途的真实世界示例如下@memoize
: