mon
mon

Reputation: 22234

Python - how to access instance properties in parent classes with super(<class>, self)?

Please explain why I cannot use super(class, self) to access the properties defined in the class higher in the class inheritance hierarchy, and how to access them.

According to the document, super(class, self should be returning a proxy object via which I can access the def name() in a parent class instance.

Return a proxy object that delegates method calls to a parent or sibling class of type. This is useful for accessing inherited methods that have been overridden in a class.

The object-or-type determines the method resolution order to be searched. The search starts from the class right after the type.

For example, if mro of object-or-type is D -> B -> C -> A -> object and the value of type is B, then super() searches C -> A -> object.

I thought super(Parent) will give a proxy to the GrandParent object which has the def name().

class GrandParent:
    def __init__(self):
        self._name = "grand parent"

    @property
    def name(self):
        return self._name


class Parent(GrandParent):
    def __init__(self):
        super().__init__()
        self._name = "parent"

    @property
    def name(self):
        return super().name


class Child(Parent):
    def __init__(self):
        super().__init__()
        self._name = "child"

    @property
    def name(self):
        return super(Parent).name


print(Child().name)
---
AttributeError: 'super' object has no attribute 'name'

Without (class, self), it returns ... the child property. Obviously I have not understood how self and super work in Python. Please suggest what resources to look into to fully understand the behavior and the design.

class GrandParent:
    def __init__(self):
        self._name = "grand parent"

    @property
    def name(self):
        return self._name


class Parent(GrandParent):
    def __init__(self):
        super().__init__()
        self._name = "parent"

    @property
    def name(self):
        return super().name


class Child(Parent):
    def __init__(self):
        super().__init__()
        self._name = "child"

    @property
    def name(self):
        return super().name


print(Child().name)
---
child

Upvotes: 0

Views: 1900

Answers (1)

deceze
deceze

Reputation: 521994

The self always refers to one and the same object. When you do super().__init__(), the self in the parent's __init__ is your instance of Child. GrandParent.__init__ just sets an attribute on that object. By chaining all those __init__s, you're in effect just doing this:

o = object()
o._name = 'grand parent'
o._name = 'parent'
o._name = 'child'

You're just overwriting the _name attribute, of which there's only one. All the different @propertys just return the value of this one _name attribute, of which your object only has one, and whose value is 'child'.

If you want your object to have a separate _name attribute per parent, you will actually have to create separate attributes. The easiest way is probably with Python's double-underscore name mangling a.k.a. "private attributes":

>>> class A:
...   def __init__(self):
...     self.__foo = 'bar'
...   @property
...   def foo(self):
...     return self.__foo
...
>>> class B(A):
...   def __init__(self):
...     super().__init__()
...     self.__foo = 'baz'
...   @property
...   def foo(self):
...     return super().foo
... 
>>> B().foo
'bar'
>>> vars(B())
{'_A__foo': 'bar', '_B__foo': 'baz'}

The actual attributes are named _A__foo and _B__foo and thereby don't conflict with each other.

Upvotes: 2

Related Questions