在Python中智能缓存昂贵对象

2 投票
2 回答
4789 浏览
提问于 2025-04-17 09:27

我有一个按顺序排列的图片文件夹。通常我的代码会使用一部分连续的图片数据(比如第5到第10张),而获取这些图片的简单方法有:

  1. 创建一个包装对象,里面有一个方法可以在需要的时候加载图片并读取我的数据(比如一个像素值)。这种方法占用的内存很少,但速度会比较慢,因为每次都需要重新加载每一张图片。

  2. 把所有的图片都存放在内存里。这种方法速度快,但显然我们能存放的图片数量是有限的。

我想找到一种方法:

  • 可以定义如何根据索引或路径读取对应的图片,然后让我可以像这样访问,比如 magic_image_collection[index],而不需要担心它是从内存中返回对象还是重新读取。这种方法最好能把合适的图片或者最近访问的 n 张图片保存在内存里。

2 个回答

2

弱引用(weakrefs)并不是你想要的东西。弱引用是一种引用方式,它允许垃圾回收器在只有弱引用存在时,回收(也就是销毁)被引用的对象。换句话说,如果你只创建并存储某个对象的弱引用,这个对象很可能会被快速回收,你就无法使用它了。

我建议你选择上面提到的选项 #1。在现代操作系统中,系统会维护一个内存缓存,用来存储最近访问过的文件(或文件的一部分)。这意味着你只需承担一次从硬盘加载文件的成本,但之后再次访问这个文件时,速度会和在你应用程序内存中一样快(或者几乎一样快)。文件系统缓存通常是基于最近最少使用(LRU)策略的,所以经常访问的文件会留在内存中,而不常访问的文件则可能会被移除(如果需要的话,之后会从硬盘重新加载)。在大多数情况下,依赖操作系统来处理这种逻辑就足够了,而不需要自己编写和维护相关代码!

6

你可以扩展默认的字典(dict),并使用 __missing__ 方法来调用一个加载函数,当你查找的键不存在时:

class ImageDict(dict):
    def __missing__(self, key):
        self[key] = img = self.load(key)
        return img
    def load(self, key):
        # create a queue if not exist (could be moved to __init__)
        if not hasattr(self, '_queue'):
            self._queue = []
        # pop the oldest entry in the list and the dict
        if len(self._queue) >= 100:
            self.pop(self._queue.pop(0))
        # append this key as a newest entry in the queue
        self._queue.append(key)
        # implement image loading here and return the image instance
        print 'loading', key
        return 'Image for %s' % key

输出结果(只有在键不存在时才会进行加载。)

>>> d = ImageDict()
>>> d[3]
loading 3
'Image for 3'
>>> d[3]
'Image for 3'
>>> d['bleh']
loading bleh
'Image for bleh'
>>> d['bleh']
'Image for bleh'

一种改进的方法是只在字典中存储最近的 N 个元素,并删除最旧的条目。你可以通过保持一个键的列表来实现这个排序。

撰写回答