Getting private attribute in parent class using super(), outside of a method

Question:

I have a class with a private constant _BAR = object().

In a child class, outside of a method (no access to self), I want to refer to _BAR.

Here is a contrived example:

class Foo:
    _BAR = object()

    def __init__(self, bar: object = _BAR):
        ...

class DFoo(Foo):
    """Child class where I want to access private class variable from parent."""

    def __init__(self, baz: object = super()._BAR):
        super().__init__(baz)

Unfortunately, this doesn’t work. One gets an error: RuntimeError: super(): no arguments

Is there a way to use super outside of a method to get a parent class attribute?


The workaround is to use Foo._BAR, I am wondering though if one can use super to solve this problem.

Answers:

Inside of DFoo, you cannot refer to Foo._BAR without referring to Foo. Python variables are searched in the local, enclosing, global and built-in scopes (and in this order, it is the so called LEGB rule) and _BAR is not present in any of them.

Let’s ignore an explicit Foo._BAR.

Further, it gets inherited: DFoo._BAR will be looked up first in DFoo, and when not found, in Foo.

What other means are there to get the Foo reference? Foo is a base class of DFoo. Can we use this relationship? Yes and no. Yes at execution time and no at definition time.

The problem is when the DFoo is being defined, it does not exist yet. We have no start point to start following the inheritance chain. This rules out an indirect reference (DFoo -> Foo) in a def method(self, ....): line and in a class attribute _DBAR = _BAR.

It is possible to work around this limitation using a class decorator. Define the class and then modify it:

def deco(cls):
    cls._BAR = cls.__mro__[1]._BAR * 2  # __mro__[0] is the class itself
    return cls 

class Foo:
    _BAR = 10

@deco
class DFoo(Foo):
    pass

print(Foo._BAR, DFoo._BAR) # 10 20

Similar effect can be achieved with a metaclass.

The last option to get a reference to Foo is at execution time. We have the object self, its type is DFoo, and its parent type is Foo and there exists the _BAR. The well known super() is a shortcut to get the parent.

I have assumed only one base class for simplicity. If there were several base classes, super() returns only one of them. The example class decorator does the same. To understand how several bases are sorted to a sequence, see how the MRO works (Method Resolution Order).

My final thought is that I could not think up a use-case where such access as in the question would be required.

Answered By: VPfB

Short answer: you can’t !

I’m not going into much details about super class itself here. (I’ve written a pure Python implementation in this gist if you like to read.)

But now let’s see how we can call super:

1- Without arguments:

From PEP 3135:

This PEP proposes syntactic sugar for use of the super type to
automatically construct instances of the super type binding to the
class that a method was defined in, and the instance (or class object
for classmethods) that the method is currently acting upon.

The new syntax:

super()

is equivalent to:

super(__class__, <firstarg>)

…and <firstarg> is the first parameter of the method

So this is not an option because you don’t have access to the "instance".
(Body of the function/methods is not executed unless it gets called, so no problem if DFoo doesn’t exist yet inside the method definition)

2- super(type, instance)

From documentation:

The zero argument form only works inside a class definition, as the
compiler fills in the necessary details to correctly retrieve the
class being defined, as well as accessing the current instance for
ordinary methods.

What were those necessary details mentioned above? A "type" and A "instance":

We can’t pass neither "instance" nor "type" which is DFoo here. The first one is because it’s not inside the method so we don’t have access to instance(self). Second one is DFoo itself. By the time the body of the DFoo class is being executed there is no reference to DFoo, it doesn’t exist yet. The body of the class is executed inside a namespace which is a dictionary. After that a new instance of type type which is here named DFoo is created using that populated dictionary and added to the global namespaces. That’s what class keyword roughly does in its simple form.

3- super(type, type):

If the second argument is a type, issubclass(type2, type) must be
true

Same reason mentioned in above about accessing the DFoo.

4- super(type):

If the second argument is omitted, the super object returned is
unbound.

If you have an unbound super object you can’t do lookup(unless for the super object’s attributes itself). Remember super() object is a descriptor. You can turn an unbound object to a bound object by calling __get__ and passing the instance:

class A:
    a = 1

class B(A):
    pass

class C(B):
    sup = super(B)
    try:
        sup.a
    except AttributeError as e:
        print(e)  # 'super' object has no attribute 'a'

obj = C()
print(obj.sup.a)  # 1

obj.sup automatically calls the __get__.

And again same reason about accessing DFoo type mentioned above, nothing changed. Just added for records. These are the ways how we can call super.

Answered By: S.B
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.