'Accesing superclass attribute during subclass construction in Python

Is it possible to access superclass's attributes during class construction? Here's my code:

class A:
   x = 1

class B(A):
   x += 1   # <- error

The increment line x += ... is not valid, because x is not defined at this moment. You may suggest doing it in __init__, but I need to initialize the values before the constructor and want to minimize the code.

The code x = A.x + 1 will not work in my case because the A is generated in run-time. So the real code for B looks like

class A:
  x: int

def class_gen(params):
  class SubA(A):
    x = 1
  return SubA

class B(class_gen(some_params)):
  x += 1

I've found a weird workaround: x = A.__subclasses__()[-1].x + 1 (indeed the last subclass of A will be the generated super), but it looks too dirty and unstable.

Another workaround is declaring the a 'stub' class variable:

...
_Stub = class_gen(some_params)
class B(_Stub):
  x = _Stub.x + 1

but is it also looks urgly. Is there a better way for a perfectionist?



Solution 1:[1]

I think you can accomplish it by using __new__ in the subclass.

class A:
    x = 1

class B(A):
    def __new__(cls, *args, **kwargs):
        cls.x += 1
        return super().__new__(cls, *args, **kwargs)

b = B()
print(b.x)  # 2

Solution 2:[2]

There's no easy way to do this. Probably the best is to give your B class a metaclass that handles incrementing of the x attribute for you:

class XIncrementerMeta(type):
    def __new__(mcls, name, bases, namespace):
        namespace['x'] = bases[0].x + 1
        return super().__new__(mcls, name, bases, namespace)

class B(class_gen("some_params"), metaclass=XIncrementerMeta):
    pass

print(B.x) # prints 2

If you need each B-like class to have its own kind of manipulation of the x value, you could put it in a classmethod and have an inherited metaclass call it. Or you could even have an __init_subclass__ method in A that calls methods to set the value on its subclasses:

class A:
    def __init_subclass__(cls):
        cls.set_X()

class SubA(A):
    @classmethod
    def set_X(cls):
        cls.x = 1

class B(SubA):
    @classmethod
    def set_X(cls):
        super().set_X()  # get SubA to set an initial value
        cls.x += 1       # then update it

print(B.x) # prints 2

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 mwo
Solution 2