延迟加载的描述符

2 投票
2 回答
803 浏览
提问于 2025-04-17 00:21

我想实现变量的懒加载,但我似乎对描述符有些误解。我希望有一些对象变量,在第一次访问时会调用 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

好吧,这里有两个问题:

  1. 你在 __get__ 方法中返回了 None,但其实应该返回你想让 x 表示的值。
  2. 你做了 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)

撰写回答