5
\$\begingroup\$

I want a Python example that illustrates how object.__getattribute__ resolves instance attribute lookups.

I'm looking for feedback about the following code. Is it a "close-enough" approximation of the object.__getattribute__ workflow using Python?

I'm aware that under the hood C is accessing the type/object slots and that the workflow is different (here). The well-written Python docs that mention the object.__getattribute__ workflow leave a lot of ambiguity about how to visualize it in Python. This ambiguity is probably legitimate because we can only talk about the workflow in terms of the C API. But isn't there a way to illustrate the call precedence using __dict__ attributes on the class and instance?

The code below tests the simulation for an instance method lookup during multiple inheritance, the real use case I'm investigating.


Given this mixins.py file:

class Boom(object):
    def log(self):
        print "[ BOOMTOWN ]: %s" % (self.__repr__())

class Basic(object):
    def log(self):
        print self.__class__

class Uno(Basic):
    pass

class Dos(Basic):
    pass

Simulate object.__getattribute__:

from mixins import *

def object_getattribute(instance, klass, attrname, klass_mro=[]):
    '''
    NOTE: the resolution workflow 
    for Class.attrname lookups is different
    '''
    print "[ INSPECTING ]: %s" % klass

    if attrname in klass.__dict__.keys():
        print "yep, in Class.__dict__"
        if ( hasattr( klass.__dict__[attrname], '__get__' ) 
             and hasattr( klass.__dict__[attrname], '__set__' ) ):
            print "yep, DATA descriptor found"
            return klass.__dict__[attrname].__get__( instance, klass )
        else:
            print "nope, not a DATA descriptor"
    else:
        print "nope, not in Class.__dict__"

    if attrname in instance.__dict__.keys():
        print "yep, instance.__dict__"
        return instance.__dict__[attrname]
    else:
        print "nope, not in instance.__dict__"

    if attrname in klass.__dict__.keys():
        print "yep, in Class.__dict__"
        if hasattr( klass.__dict__[attrname], '__get__' ):
            print "yep, NON-DATA descriptor found"
            return klass.__dict__[attrname].__get__( instance, klass )
        else:
            print "return from Class.__dict__[ attrname ]"
            return klass.__dict__[attrname]
    else:
        print "nope, not in Class.__dict__"

    if hasattr( klass, '__getattr__' ):
        print "return from Class.__getattr__( attrname )"
        return klass.__getattr__( attrname )
    else:
        print "nope, no __getattr__ override"

    return object_getattribute( instance, 
    klass_mro.pop(0), attrname, klass_mro=klass_mro )

Use the simulation to inspect how instance methods are looked up in multiple inheritance situations:

class Foo(Uno,Dos): pass
func = object_getattribute( Foo(), Foo, 'log', klass_mro=Foo.mro()[1:] )
func()
print "\n"

class Foo(Boom,Uno,Dos): pass
func = object_getattribute( Foo(), Foo, 'log', klass_mro=Foo.mro()[1:] )
func()
print "\n"

class Foo(Uno,Boom,Dos): pass
func = object_getattribute( Foo(), Foo, 'log', klass_mro=Foo.mro()[1:] )
func()
print "\n"

class Foo(Uno,Dos,Boom): pass
func = object_getattribute( Foo(), Foo, 'log', klass_mro=Foo.mro()[1:] )
func()
print "\n"

The output:

[ INSPECTING ]: <class '__main__.Foo'>
nope, not in Class.__dict__
nope, not in instance.__dict__
nope, not in Class.__dict__
nope, no __getattr__ override
[ INSPECTING ]: <class 'mixins.Uno'>
nope, not in Class.__dict__
nope, not in instance.__dict__
nope, not in Class.__dict__
nope, no __getattr__ override
[ INSPECTING ]: <class 'mixins.Dos'>
nope, not in Class.__dict__
nope, not in instance.__dict__
nope, not in Class.__dict__
nope, no __getattr__ override
[ INSPECTING ]: <class 'mixins.Basic'>
yep, in Class.__dict__
nope, not a DATA descriptor
nope, not in instance.__dict__
yep, in Class.__dict__
yep, NON-DATA descriptor found
<class '__main__.Foo'>


[ INSPECTING ]: <class '__main__.Foo'>
nope, not in Class.__dict__
nope, not in instance.__dict__
nope, not in Class.__dict__
nope, no __getattr__ override
[ INSPECTING ]: <class 'mixins.Boom'>
yep, in Class.__dict__
nope, not a DATA descriptor
nope, not in instance.__dict__
yep, in Class.__dict__
yep, NON-DATA descriptor found
[ BOOMTOWN ]: <__main__.Foo object at 0x102b0f050>


[ INSPECTING ]: <class '__main__.Foo'>
nope, not in Class.__dict__
nope, not in instance.__dict__
nope, not in Class.__dict__
nope, no __getattr__ override
[ INSPECTING ]: <class 'mixins.Uno'>
nope, not in Class.__dict__
nope, not in instance.__dict__
nope, not in Class.__dict__
nope, no __getattr__ override
[ INSPECTING ]: <class 'mixins.Boom'>
yep, in Class.__dict__
nope, not a DATA descriptor
nope, not in instance.__dict__
yep, in Class.__dict__
yep, NON-DATA descriptor found
[ BOOMTOWN ]: <__main__.Foo object at 0x102b06fd0>


[ INSPECTING ]: <class '__main__.Foo'>
nope, not in Class.__dict__
nope, not in instance.__dict__
nope, not in Class.__dict__
nope, no __getattr__ override
[ INSPECTING ]: <class 'mixins.Uno'>
nope, not in Class.__dict__
nope, not in instance.__dict__
nope, not in Class.__dict__
nope, no __getattr__ override
[ INSPECTING ]: <class 'mixins.Dos'>
nope, not in Class.__dict__
nope, not in instance.__dict__
nope, not in Class.__dict__
nope, no __getattr__ override
[ INSPECTING ]: <class 'mixins.Basic'>
yep, in Class.__dict__
nope, not a DATA descriptor
nope, not in instance.__dict__
yep, in Class.__dict__
yep, NON-DATA descriptor found
<class '__main__.Foo'>
\$\endgroup\$
  • \$\begingroup\$ Does the code already work like you expect it to, or you asking about possible fixes to the current behaviour? \$\endgroup\$ – ferada Nov 30 '15 at 22:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.