Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/ms-access/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 如何实施临时功能“;回忆录;?_Python_Python 2.7_Memoization - Fatal编程技术网

Python 如何实施临时功能“;回忆录;?

Python 如何实施临时功能“;回忆录;?,python,python-2.7,memoization,Python,Python 2.7,Memoization,要记忆的函数不是“纯”(它的返回值将来可能会改变),因此我不能使用装饰。 此外,我需要它所调用的值的列表 我所做的是 def f(...): cache = {} for ...: try: x = cache[k] except KeyError: x = cache[k] = expensive(k) # use x here for x in cache.itervalu

要记忆的函数不是“纯”(它的返回值将来可能会改变),因此我不能使用装饰。 此外,我需要它所调用的值的列表

我所做的是

def f(...):
    cache = {}
    for ...:
        try:
            x = cache[k]
        except KeyError:
            x = cache[k] = expensive(k)
        # use x here
    for x in cache.itervalues():
        cleanup(x)
我想知道这是否是表达范式的“pythonic”方式

例如,我可以通过写作节省3行

def f(...):
    cache = {}
    for ...:
        x = cache[k] = cache.get(k) or expensive(k)
        # use x here
    for x in cache.itervalues():
        cleanup(x)
相反(假设
None
0
[]
{}
和其他假值不可能是
昂贵的返回值


这看起来更好吗?

除了版本之外,我会坚持使用
try
/
版本,因为对
昂贵的返回值的假设是真实的,这对于一般化来说是一个坏主意(从性能角度来看,作为一个实现细节,
d[k]
d.get(k)更快)
在CPython上,异常的成本通常与条件检查的成本相当,更不用说
昂贵的
函数旁边的所有噪声了)。不过,我会做一个调整,在两个线程竞争时统一结果,并且最终都会计算昂贵的结果,以避免它们各自收到自己的(可能昂贵的)结果副本。将
除KeyError
处理程序中的行更改为:

x = cache[k] = expensive(k)
致:

这样做,如果两个线程同时开始计算
昂贵的
,第一个线程将存储缓存的值,第二个线程将立即丢弃自己的结果,以支持第一个线程存储的缓存值。如果计算结果的成本很高,而不是每个实例的内存或其他资源成本很高,那么这不会造成任何伤害;如果计算结果在其他方面很高,则会很快消除重复的值

除非
k
是一个C级内置函数,否则它在CPython上实际上不是100%线程安全的(因为在理论上,当执行Python级别的
\uEQ\uEQ
函数以解决冲突时,在真正病态的情况下可能会触发一些竞争条件
setdefault
),但最糟糕的情况是重复数据消除无法工作

如果您不喜欢函数本身中包含的所有kruft,那么一个很好的解决方法是使用您自己的
dict
子类,该子类遵循
集合的一般模式。defaultdict
(但使用键作为计算默认值的一部分)。这并不难,因为
\uuu missing\uuu
hook
dict
提供了:

# Easiest to let defaultdict define the alternate constructor and attribute name
from collections import defaultdict

class CacheDict(defaultdict):
    def __missing__(self, key):
        # Roughly the same implementation as defaultdict's default
        # __missing__, but passing the key as the argument to the factory function
        return self.setdefault(key, self.default_factory(key))
编写了该类之后,您可以使用更少的缓存相关kruft编写函数:

def f(...):
    cacheorcompute = CacheDict(expensive)
    for ...:
        x = cacheorcompute[k]
        # use x here
    for x in cacheorcompute.itervalues():
        cleanup(x)

ShadowRanger的答案可能是你正在寻找的,但是我也会考虑在一个地方做设置和清理任务,并使用< <代码> x>代码>在其他地方使用:

。 现在您当然可以这样做:

>>> f(...)
…然而,现在我们已经分离出设置/拆卸,如果我们想使用
x
s(而不是
f
)执行我们以前可能没有考虑过的其他任务,包括
g(x)
h(x)
,我们可以稍后再回到这段代码:


因此,这是一个多一点的代码,但它为您提供了更多的可能性。

@timgeb:首先,我使用的是python 2,而不是python 3。第二,缓存必须是我的函数
f
的本地缓存。因此,要清楚地说,每个
缓存
只能在其函数调用进行时才有效?@timgeb:是的,本地缓存only@glibdud:随便你怎么称呼它。问题在于样式。请注意,
cache.get(k)
可能会返回除
None
之外的其他错误值,例如
'
0
[]
。经过一些考虑,我认为“尝试/例外”的方法很好。如果缓存中有k,你也可以使用
。我喜欢你的CacheDict建议!
from contextlib import contextmanager

@contextmanager
def xs_manager(...):
    """Manages setup/teardown of cache of x's"""
    # setup
    cache = {}
    def gencache():
        """Inner generator for passing each x outside"""
        for ...:
            try:
                x = cache[k]
            except KeyError:
                x = cache[k] = expensive(k)
            yield x
    yield gencache()
    # external use of x's occurs here
    # teardown
    for x in cache.itervalues():
        cleanup(x)

def f(...):
    with xs_manager(...) as xvaluecache:
        for x in xvaluecache:
            # use x here
>>> f(...)
>>> with xs_manager(...) as xvaluecache:
...    for x in xvaluecache:
...        g(x)
...        h(x)