2

Newbie python inheritance question.. when you write the following:

class A:
    def foo(self):
        print("a")

class B(A):
    pass

b = B()
getattr(b, "foo")

produces

<bound method A.foo of <__main__.B object at 0x000000000604CC88>>

It looks as if Python creates an attribute foo which is bound to A.foo and it knows that foo is an A method.

Should I think of inheritance as simply creating pointers to the base class objects (and the interpreter knows it's a base class object) as opposed to creating a copy of the attribute in the deriving class?

Seems like this is an important point but somehow wasn't clear to me initially - came to this question while playing around with super().

7
  • When you do b.foo, Python will first look in the __dict__ of b where it won’t find a foo. Then it will check the mro(), i.e. all the types in the type hierachy in order, to check where it could take a foo from. In your case, it would first check B.foo and then A.foo. Commented Apr 16, 2018 at 20:51
  • @poke right so i think the crux of my question was: when you do B(A), foo isn't added to B's namespace, rather __dict__ contains all the attributes that were created specifically within B and not inherited from elsewhere, yeh? Commented Apr 16, 2018 at 20:54
  • 1
    The __dict__ will only contain instance members, i.e. things where you did self.something = value. Methods will usually live on the class, not the instance. Commented Apr 16, 2018 at 20:55
  • @poke good to know! so then the correct way of saying it is foo isn't added to B.__dict__ when you do B(A), rather when you call B.foo, the B.__dict__ is checked and then further up the MRO since it wasn't found? Commented Apr 16, 2018 at 20:59
  • 1
    Yes, any attribute access will work like that. Commented Apr 16, 2018 at 21:13

1 Answer 1

1

You are partially correct, let's start by what you got wrong.

Should I think of inheritance as simply creating pointers to the base class objects

That would be incorrect. When you access an attribute, either with getattr(obj, attr) or by doing obj.attr, Python first recovers the attribute through the method resolution order. In particular this means that this does not happen at class creation, but at attribute lookup instead.

It looks as if Python creates an attribute foo which is bound to A.foo

Here you are correct. Binding a class or instance method created a new object in the process.

After having recovered the attribute, Python may need to bind it. It first checks if it is a descriptor, that is an object with a __get__ method to allow binding it to a certain instance.

Here is a Python implementation of the getattr to help visualize what happens when you retrieve an attribute.

First this is how Python resolves a class attribute using the mro.

def _resolve_cls_attr(cls, attr):
    if attr in cls.__dict__:
        return cls.__dict__[attr]

    elif len(cls.__mro__) > 1:
        return _resolve_cls_attr(cls.__mro__[1], attr)

    else:
        raise AttributeError('class has no attribute {}'.format(attr))

Then, this is how getattr would be implemented if it were in Python.

def getattr(instance, attr):
    cls = instance if isinstance(instance, type) else type(instance)

    # Recover instance attribute
    if hasattr(instance, '__dict__') and attr in instance.__dict__:
        attr_value = instance.__dict__[attr]

    # Recover the attribute from the class
    else:
        attr_value = _resolve_cls_attr(cls, attr)

    # We recovered a simple class attribute
    if not hasattr(attr_value, '__get__'):
        return attr_value

    # We recovered an instance method from a class or a staticmethod descriptor
    if instance is cls or isinstance(attr_value, staticmethod):
        return attr_value.__get__(None, cls)

    # We recovered an instance method or a custom descriptor
    return attr_value.__get__(instance, cls)

Keep in mind that the above omits a few steps for the sake of sticking to your question. By example, it will not rely on __getattr__ and __getattribute__ as the builtin getattr would.

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

2 Comments

This is a pretty misleading and buggy way to handle descriptors. Why all the special-casing instead of just implementing the actual rule (__get__(instance, cls) for an instance, __get__(None, cls) for a class)? Also, if you're going to do descriptor handling, data descriptors are probably at least worth a mention.
@user2357112 I wrote that around 1am and could improve it a bit after a good night of sleep. Note that doc to descriptors is already linked in the answer.

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.