如何懒加载数据结构(python)

10 投票
6 回答
13424 浏览
提问于 2025-04-16 09:11

我有一种方法可以根据一些文件内容来构建数据结构:

def loadfile(FILE):
    return # some data structure created from the contents of FILE

所以我可以做一些事情,比如:

puppies = loadfile("puppies.csv") # wait for loadfile to work
kitties = loadfile("kitties.csv") # wait some more
print len(puppies)
print puppies[32]

在上面的例子中,我浪费了很多时间去读取 kitties.csv 文件,并创建了一个我从未使用过的数据结构。我想避免这种浪费,而不想每次想做点什么时都要检查 if not kitties。我希望能够这样做:

puppies = lazyload("puppies.csv") # instant
kitties = lazyload("kitties.csv") # instant
print len(puppies)                # wait for loadfile
print puppies[32]

这样,如果我从来没有尝试去使用 kitties,那么 loadfile("kitties.csv") 就不会被调用。

有没有什么标准的方法可以做到这一点呢?

在玩了一段时间之后,我得出了以下解决方案,看起来是正确的,而且相当简洁。有没有其他的选择?使用这种方法有什么缺点是我需要注意的吗?

class lazyload:
    def __init__(self,FILE):
        self.FILE = FILE
        self.F = None
    def __getattr__(self,name):
        if not self.F: 
            print "loading %s" % self.FILE
            self.F = loadfile(self.FILE)
        return object.__getattribute__(self.F, name)

如果能有这样的效果就更好了:

class lazyload:
    def __init__(self,FILE):
        self.FILE = FILE
    def __getattr__(self,name):
        self = loadfile(self.FILE) # this never gets called again
                                   # since self is no longer a
                                   # lazyload instance
        return object.__getattribute__(self, name)

但这不行,因为 self 是局部的。实际上,每次你做任何事情时,它都会调用 loadfile

6 个回答

1

难道 if not self.F 不会导致再次调用 __getattr__,让你陷入无限循环吗?我觉得你的做法是有道理的,但为了保险起见,我会把那一行改成:

if name == "F" and not self.F:

另外,你可以把 loadfile 变成这个类的一个方法,这要看你具体在做什么。

2

如果你对这个if语句感到很担心,那说明你有一个有状态的对象。

from collections import MutableMapping
class LazyLoad( MutableMapping ):
   def __init__( self, source ):
       self.source= source
       self.process= LoadMe( self )
       self.data= None
   def __getitem__( self, key ):
       self.process= self.process.load()
       return self.data[key]
   def __setitem__( self, key, value ):
       self.process= self.process.load()
       self.data[key]= value
   def __contains__( self, key ):
       self.process= self.process.load()
       return key in self.data

这个类把工作交给一个process对象,这个对象要么是Load,要么是DoneLoadingLoad对象会真正去加载东西,而DoneLoading对象则不会去加载。

注意,这里没有使用if语句。

class LoadMe( object ):
   def __init__( self, parent ):
       self.parent= parent
   def load( self ):
       ## Actually load, setting self.parent.data
       return DoneLoading( self.parent )

class DoneLoading( object ):
   def __init__( self, parent ):
       self.parent= parent
   def load( self ):
       return self
6

Python标准库中的csv模块在你开始遍历数据之前不会加载数据,所以它实际上是懒加载的。

补充一下:如果你需要读取整个文件来构建数据结构,使用一个复杂的懒加载对象来代理这些事情就有点过于复杂了。你可以直接这样做:

class Lazywrapper(object):
    def __init__(self, filename):
        self.filename = filename
        self._data = None

    def get_data(self):
        if self._data = None:
            self._build_data()
        return self._data

    def _build_data(self):
        # Now open and iterate over the file to build a datastructure, and
        # put that datastructure as self._data

使用上面的类,你可以这样做:

puppies = Lazywrapper("puppies.csv") # Instant
kitties = Lazywrapper("kitties.csv") # Instant

print len(puppies.getdata()) # Wait
print puppies.getdata()[32] # instant

另外

allkitties = kitties.get_data() # wait
print len(allkitties)
print kitties[32]

如果你有很多数据,并且并不需要加载所有的数据,你也可以实现一个类,它会读取文件直到找到名为“Froufrou”的小狗,然后停止。不过在这种情况下,可能更好的是把数据一次性放进数据库里,然后从数据库中访问。

撰写回答