如何懒加载数据结构(python)
我有一种方法可以根据一些文件内容来构建数据结构:
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 个回答
难道 if not self.F
不会导致再次调用 __getattr__
,让你陷入无限循环吗?我觉得你的做法是有道理的,但为了保险起见,我会把那一行改成:
if name == "F" and not self.F:
另外,你可以把 loadfile 变成这个类的一个方法,这要看你具体在做什么。
如果你对这个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
,要么是DoneLoading
。Load
对象会真正去加载东西,而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
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”的小狗,然后停止。不过在这种情况下,可能更好的是把数据一次性放进数据库里,然后从数据库中访问。