如何访问惰性变量类的属性?

1 投票
1 回答
594 浏览
提问于 2025-04-17 05:50

我自己做了一个懒加载变量的类,并在另一个类中使用了它。那我该怎么访问这个懒加载变量类的属性呢?我试过用 __getattr__,但没成功。下面是一个例子:

class lazyobject(object):
    def __init__(self,varname,something='This is the something I want to access'):
        self.varname = varname
        self.something = something

    def __get__(self, obj, type=None):
        if obj.__dict__.has_key(self.varname):
            print "Already computed %s" % self.varname
            return obj.__dict__[self.varname]
        else:
            print "computing %s" % self.varname
            obj.__dict__[self.varname] = "something else"
            return obj.__dict__[self.varname]

class lazyobject2(lazyobject):
    def __getattr__(self):
        return self.something

class dummy(object):
    def __init__(self):
        setattr(self.__class__, 'lazy', lazyobject('lazy'))

class dummy2(object):
    def __init__(self):
        setattr(self.__class__, 'lazy', lazyobject2('lazy'))

d1 = dummy()
d2 = dummy2()

try:
    print "d1.lazy.something - no getattr: ",d1.lazy.something
except:
    print "d2.lazy is already computed - can't get its .something because it's now a string!"
print "d1.lazy - no getattr: ",d1.lazy

try:
    print "d2.lazy.something - has getattr: ",d2.lazy.something
except:
    print "d2.lazy is already computed - can't get its .something because it's now a string!"
print "d2.lazy - no getattr: ",d2.lazy

这个例子打印出来的是:

d1.lazy.something - no getattr:  computing lazy
d2.lazy is already computed - can't get its .something because it's now a string!
d1.lazy - no getattr:  something else
d2.lazy.something - has getattr:  computing lazy
d2.lazy is already computed - can't get its .something because it's now a string!
d2.lazy - no getattr:  something else

我希望它打印的是:

d1.lazy.something - no getattr:  This is the something I want to access
computing lazy
d1.lazy - no getattr:  something else

上面的例子虽然有点牵强,但我希望能表达我的意思。换句话说,我的问题是:在访问类的属性时,怎么绕过 __get__ 方法?

1 个回答

4

要绕过 __get__ 访问类属性的方法是通过类字典查找,而不是使用点号访问。

用函数对象来演示这个概念很简单。例如:

>>> class A(object):
        def f(self):
            pass

>>> A.f                         # dotted access calls f.__get__
<unbound method A.f>
>>> vars(A)['f']                # dict access bypasses f.__get__
<function f at 0x101723500>

>>> a = A()
>>> a.f                         # dotted access calls f.__get__
<bound method A.f of <__main__.A object at 0x10171e810>>
>>> vars(a.__class__)['f']      # dict access bypasses f.__get__
<function f at 0x101723500>

你缺少的另一个信息是,继承的 __get____getattr__ 之前运行,只有在找不到属性时才会运行 __getattr__。这个逻辑是由从 object 继承来的 __getattribute__ 控制的。所以,如果你想绕过 __get__,你需要在子类中写一个新的 __get__,或者通过在子类中定义 __getattribute__ 来改变查找逻辑。

要修复 lazyobject2 类,可以将 __getattr__ 替换为:

class lazyobject2(lazyobject):

    def __getattribute__(self, key):
        # bypass __get__
        return object.__getattribute__(self, '__dict__')[key]

总之,解决这个问题的关键知识点有:

  • object.__getattribute__ 控制查找逻辑。
  • 它首先查找 __get__,无论是在当前类中定义还是继承的。
  • 只有在什么都没找到的情况下,它才会尝试调用 object.__getattr__
  • 以上三个步骤仅在使用点号查找时发生。
  • 可以通过直接访问 __dict__vars() 来绕过这些步骤。

关于描述符逻辑的详细信息可以在 这篇文章这个演示文稿 中找到。

撰写回答