Python 中实现延迟加载
2018 年 05 月 10 日延迟加载 (lazy loading) 是一种仅在资源第一次调用时才进行真正加载的模式。资源加载通常会涉及到较慢的 I/O 操作,并且会占用内存,因此合理使用延迟加载可以有效地节约初始化时间和内存消耗,特别是在资源并不是每次都会全部用到的情况。
本文将讨论在 Python 中实现延迟加载的一些思路。
预加载的例子
下面这个例子在初始化时一次性加载所有资源。
def load(name):
return 'Content of ' + name
class Eager(object):
def __init__(self):
self.__first = load('first')
self.__second = load('second')
@property
def first(self):
return self.__first
@property
def second(self):
return self.__second
e = Eager()
print(e.first)
print(e.second)
延迟加载例子
这个例子里资源仅在第一次调用时才会载入内存。
class Lazy1(object):
def __init__(self):
self.__first = None
self.__second = None
@property
def first(self):
if self.__first is None:
self.__first = load('first')
return self.__first
@property
def second(self):
if self.__second is None:
self.__second = load('second')
return self.__second
lazy1 = Lazy1()
print(lazy1.first)
print(lazy1.second)
使用 __getattr__
上面的例子看起来有些罗嗦,__getattr__
可以简化这一实现。
class Lazy2(object):
def __init__(self):
self.__data = {}
def __getattr__(self, name):
if name not in self.__data:
self.__data[name] = load(name)
return self.__data[name]
def __setattr__(self, name, value):
if not name.startswith('_'):
raise AttributeError("can't set attribute")
object.__setattr__(self, name, value)
lazy2 = Lazy2()
print(lazy2.first)
print(lazy2.second)
延迟加载 Python dict
如果想用类似字典映射的方式进行延迟加载,可以通过继承
Mapping
实现。
try:
from collections.abc import Mapping
except ImportError:
from collections import Mapping
class Lazy3(Mapping):
def __init__(self):
self.__data = {}
def __getitem__(self, name):
if name not in self.__data:
self.__data[name] = load(name)
return self.__data[name]
def __iter__(self):
return iter(self.__data)
def __len__(self):
return len(self.__data)
lazy3 = Lazy3()
print(lazy3['first'])
print(lazy3['second'])
上面这个对象不能直接修改内容,因此可以保护内容完整性。如果希望允许修改,可以把 Mapping
换成
MutableMapping
。