我从下面链接获得的代码,可以将数据保存到磁盘。
http://tohyongcheng.github.io/python/2016/06/07/persisting-a-cache-in-python-to-disk.html
我尝试了但文件没有生成。
import atexit
import pickle
# or import cPickle as pickle
def persist_cache_to_disk(filename):
def decorator(original_func):
try:
cache = pickle.load(open(filename, 'r'))
except (IOError, ValueError):
cache = {}
atexit.register(lambda: pickle.dump(cache, open(filename, "w")))
def new_func(*args):
if tuple(args) not in cache:
cache[tuple(args)] = original_func(*args)
return cache[args]
return new_func
return decorator
我尝试按照示例使用此代码...
@persist_cache_to_disk('users.p')
def get_all_users():
x = 'some user'
return x
更新
这是在python命令提示符下工作,但在ipython notebook中不起作用。
答案 0 :(得分:11)
问题是该示例使用atexit
仅在python退出时运行转储例程。每次更新缓存时,此修改版本都将转储:
import atexit
import functools
import pickle
# or import cPickle as pickle
def persist_cache_to_disk(filename):
def decorator(original_func):
try:
cache = pickle.load(open(filename, 'r'))
except (IOError, ValueError):
cache = {}
# Your python script has to exit in order to run this line!
# atexit.register(lambda: pickle.dump(cache, open(filename, "w")))
#
# Let's make a function and call it periodically:
#
def save_data():
pickle.dump(cache, open(filename, "w"))
# You should wrap your func
@functools.wraps(original_func)
def new_func(*args):
if tuple(args) not in cache:
cache[tuple(args)] = original_func(*args)
# Instead, dump your pickled data after
# every call where the cache is changed.
# This can be expensive!
save_data()
return cache[args]
return new_func
return decorator
@persist_cache_to_disk('users.p')
def get_all_users():
x = 'some user'
return x
get_all_users()
如果您想限制保存,可以修改save_data()
仅保存,例如,当len(cache.keys())
是100的倍数时。
我还为你的装饰者添加了functools.wraps
。来自docs:
如果不使用这个装饰工厂,示例函数的名称就是'包装',原始example()的docstring就会丢失。
答案 1 :(得分:5)
最佳解决方案取决于用例。没有一般方法可以立即解决所有问题。
如果要加速函数调用,可能需要将结果缓存在内存中(因为磁盘读/写速度也很慢)。如果要调用具有相同参数的函数,则自上次启动Python解释器以来的第一次调用将变慢。所有后续调用都将访问缓存(如果您的缓存足够大以存储所有结果)。
使用Python> = 3.2甚至还有一个内置装饰器@functools.lru_cache(maxsize=100, typed=False)
:
Decorator用一个memoizing callable来包装一个函数,它可以节省maxsize最近的调用。当使用相同的参数定期调用昂贵的或I / O绑定函数时,它可以节省时间。
示例:
@lru_cache(maxsize=32)
def get_pep(num):
'Retrieve text of a Python Enhancement Proposal'
resource = 'http://www.python.org/dev/peps/pep-%04d/' % num
try:
with urllib.request.urlopen(resource) as s:
return s.read()
except urllib.error.HTTPError:
return 'Not Found'
>>> for n in 8, 290, 308, 320, 8, 218, 320, 279, 289, 320, 9991:
... pep = get_pep(n)
... print(n, len(pep))
>>> get_pep.cache_info()
CacheInfo(hits=3, misses=8, maxsize=32, currsize=8)
有一个backport for Python 2.7 on pypi和cachetools包,它也兼容Python 2.7,还包含Python 3 @ functools.lru_cache函数装饰器的变体。
如果要在Python过程完成后保留数据,则将数据存储在磁盘上是有意义的。这可能会加快第一个函数调用,但它可能会减慢所有其他函数调用,因为它需要读取和写入文件。
@ rrauenza的解决方案看起来不错。有一些小的改进:
import pickle
import functools
import collections
# or import cPickle as pickle
def persist_cache_to_disk(filename):
def decorator(original_func):
try:
cache = pickle.load(open(filename, 'r'))
except (IOError, ValueError):
cache = {}
def save_data():
pickle.dump(cache, open(filename, "w"))
@functools.wraps(original_func)
def new_func(*args):
try:
try:
hash(args)
except TypeError:
# do not use cache because we cannot hash args
return original_func(*args)
if tuple(args) not in cache:
cache[tuple(args)] = original_func(*args)
# dump complete cache, this can be expensive!
save_data()
return cache[args]
return new_func
return decorator
函数调用也在内存中缓存,类似于@ functools.lru_cache(),但它没有实现最大缓存大小(程序内存使用的潜在问题),也没有类似typed
选项的内容(见上文)。
不幸的是shelve(由@Aya建议)不能直接使用,因为只支持字符串作为键。这应该会带来更好的性能,因为它不需要在每次更新时都写入完整的缓存。
如果用例不是缓存,那么Pickle不是首选的方法,而是在Python解释器启动之间存储数据。如果您必须更改腌制对象的类,则腌制文件将变得无用。在这种情况下可以清除缓存,但在其他情况下,请考虑使用yml,json或xml,或者如果您有大量数据,则使用某种二进制格式(例如hdf5)。
所有参数都必须是可清除的。例如,列表和词典不可清除。对此没有简单而通用的解决方案。仔细考虑需要支持哪种参数。列表可以轻松转换为元组。也适用于字典可以制作。不幸的是,这适用于上面的所有缓存方法(包括内置的@ functools.lru_cache)。
需要将数据序列化以存储在磁盘上。这通常通过使用pickle模块来完成。 shelve内部也使用pickle。不幸的是not every object can be pickled。如果函数包含不可选择的对象,您可以尝试使它们成为可选择的,或者选择不同的方式来序列化数据(以及用于存储序列化数据的不同文件格式)。如果你使用numpy对象numnpy.save()是一种非常快速的方法来存储大量数据。
对象可能相同,但不是同一类型。如果你的函数还取决于输入参数的类型,你可能会遇到麻烦:
@functools.lru_cache(typed=False)
def fun_with_numbers(a, b):
return a/b, isinstance(3, float)
division fails only with Python 2:
>>> fun_with_numbers(1, 3)
0, False
>>> fun_with_numbers(1., 3.)
0, False
使用@ functools.lru_cache(),你可以通过设置typed=True
来解决这个问题,但是如果你使用不同的缓存方法,你可能需要自己实现类似的东西。
由于显而易见的原因,该函数不应依赖于非常量全局变量或其他外部参数。如果函数返回time.time()
,它将始终返回第一个函数调用的缓存时间。
如果在没有正确锁定的情况下同时使用缓存函数,则会发生非常糟糕的事情。
在添加缓存之前和之后,您应该profiling。如果代码很快,缓存可能会降低代码的速度。