延迟加载的描述符
我想实现变量的懒加载,但我似乎对描述符有些误解。我希望有一些对象变量,在第一次访问时会调用 obj.load() 函数,这个函数会用真实的值来初始化这些变量。我写了
class ToLoad(object):
def __init__(self, loader_name="load")
self.loader_name=loader_name
def __get__(self, obj, type):
if not (hasattr(obj, "_loaded") and obj._loaded):
obj._loaded=True
getattr(obj, self.loader_name)()
return None
class Test(object):
x=ToLoad()
def __init__(self, y):
self.y=y
def load(self):
print("Loading {}".format(self.y))
self.x=self.y
t1=Test(1)
t2=Test(2)
print("A", t1.x)
print("B", t2.x)
print("C", t1.x)
但是在加载时,至少第一次并没有返回实际的值。有人能建议我其他解决这个问题的方法吗?我不太确定在get中怎么返回正确的值,因为那时我并不知道这个属性叫“x”?还有其他方法吗?
真是的,连自己的问题都无法回答……所以这里是我的 编辑: 谢谢大家的建议!不过,我的 load() 函数并不返回变量本身,因为它会加载很多不同的变量。我想尽量简化懒加载的使用方式。所以我想出了一个装饰器
class to_load:
def __init__(self, *vars, loader="load"):
self.vars=vars
self.loader=loader
def __call__(self, cls):
def _getattr(obj, attr):
if attr in self.vars:
getattr(obj, self.loader)()
return getattr(obj, attr)
else:
raise AttributeError
cls.__getattr__=_getattr
return cls
@to_load("a", "b")
class Test:
def load(self):
print("Loading")
self.a=1
self.b=2
t=Test()
print("Starting")
print(t.a)
print(t.b)
#print(t.c)
这样可以吗?我不确定这样做是否会出问题。
2 个回答
0
你可能想要的效果更像这样:
class Test(object):
def __init__(self, y):
self.y=y
def __getattr__(self, attr):
return self.load(attr)
def load(self, attr):
print("Loading `{}`".format(attr)) # ie "Loading `x`"
# get the value for `attr` somewhere, here always self.y
val = self.y
# store it on this object to avoid reloading it
setattr(self, attr, val)
return val
t1=Test(1)
t2=Test(2)
print("A", t1.x)
print("B", t2.x)
print("C", t1.x)
为了让你的代码正常工作,你需要加几个 return
:
class ToLoad(object):
def __init__(self, loader_name="load"):
self.loader_name=loader_name
def __get__(self, obj, type):
if not (hasattr(obj, "_loaded") and obj._loaded):
obj._loaded=True
return getattr(obj, self.loader_name)()
return None
class Test(object):
x=ToLoad()
def __init__(self, y):
self.y=y
def load(self):
print("Loading {}".format(self.y))
self.x=self.y
return self.x
t1=Test(1)
t2=Test(2)
print("A", t1.x)
print("B", t2.x)
print("C", t1.x)
描述符知道它们存储在哪个对象上,但不知道存储在哪个属性上。你想要的是拦截对某个属性的访问,而不是改变返回的值,所以你应该使用 __getattr__
,而不是描述符。
1
好吧,这里有两个问题:
- 你在
__get__
方法中返回了None
,但其实应该返回你想让x
表示的值。 - 你做了
x = y
,但是你的描述符没有实现__set__
方法。
所以,与其设置一个“已加载”的标志,不如创建一个实际值的属性,并检查这个属性。如果你不想让它是只读的,你应该实现 __set__
方法。否则,在 load
方法中,不要用 self.x = self.y
,而是返回这个值,让 __get__
来处理赋值。
class ToLoad(object):
def __init__(self, var, func):
self.var = var
self.func = func
# style note: try to avoid overshadowing built-ins (e.g. type)
def __get__(self, obj, cls):
try:
return getattr(obj, self.var)
except AttributeError:
value = getattr(obj, self.func)()
setattr(obj, self.var, value)
return value
class Foo(object):
x = ToLoad('x', '_load_x')
def __init__(self, y):
self.y = y
def _load_x(self):
print('Loading {0} into x'.format(self.y))
return self.y
a = Foo(1)
b = Foo(2)
print(a.x)
print(b.x)
print(a.x)