1

I've made myself a lazy variable class, and used it in another class. How can I then access the attributes of the lazy variable class? I have tried __getattr__ without luck. Here's an example:

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

This prints:

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

What I would like it to print:

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

The above example is contrived but I hope gets the point across. Another way to phrase my question is: How can I bypass the __get__ method when accessing a class attribute?

1 Answer 1

4

The way to bypass __get__ when accessing a class attribute is to look it up via the class dictionary rather than using dotted access.

This is easy to demonstrate using function objects. For example:

>>> 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>

The other piece of information you were missing is that the inherited __get__ runs before the __getattr__ which only runs if no attribute is found. This logic is controlled by __getattribute__ which is inherited from object. So, if you want to bypass __get__ you will either need to write a new __get__ in the subclass or change the lookup logic by defining __getattribute__ in the subclass.

To fix the lazyobject2 class, replace the __getattr__ with:

class lazyobject2(lazyobject):

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

In summary, the key pieces of knowledge used to solve this problem are:

  • object.__getattribute__ controls the lookup logic.
  • It first looks for __get__ whether defined in the current class or inherited.
  • Only if nothing is found, does it attempt to call object.__getattr__.
  • The above three steps only happen for dotted lookup.
  • Those step can be bypassed by directly accessing the dict via __dict__ or vars().

The full details of descriptor logic can be found in this writeup or in this presentation.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.