Search code examples
pythonclassinheritancesubclassinit

How can I call a class variable instead of the child variable with the same name?


As always, I start by saying I'm not an expert or professional in any sense, so please judge me tender. Unfortunately, I couldn't find the solution to this problem, but I'm sure this is not the first time it has appeared here. Therefore, I would appreciate it if you referred me to the relevant thread.

I have a 12 story structure of classes and subclasses. Something like:

class A:
    pass
class B(A):
    pass
#class M,N,O(A)....
class C(B):
    pass

and so on.

Each class and subclass may have or not have (usually they do have) a property (memory['prop']). and every init of any object refers to its super class.

class A:
    memory={'prop':{'a':0,'b':1}}
    def __init__(self,**kwargs):
       for k in kwargs|self.memory['prop']:
            if k not in self.__dict__:
                setattr(self,k,(kwargs|self.memory['prop'])[k])
class B(A):
    def __init__(self,**kwargs):
        super().__init__(**kwargs)
        for k in self.memory['prop']:
            if k not in self.__dict__:
                setattr(self,k,(self.memory['prop'])[k])
class C(B):
    memory={'prop':{'c':2,'d':3}}
    def __init__(self,**kwargs):
        super().__init__(**kwargs)
        for k in self.memory['prop']:
            if k not in self.__dict__:
                setattr(self,k,(self.memory['prop'])[k])

The problem here is that, if I create a C object, it will call C.memory['prop'] every time the algorithm calls the variable within an init function. What I need is to call the 'current' class memory.

In the example, if I run object=C(**{'e'=4}), I will get an object with attributes 'c', 'd' and 'e', but not 'a' or 'b'.

I know I can do this by using:

class A:
    memory={'prop':{'a':0,'b':1}}
    def __init__(self,**kwargs):
       for k in kwargs|A.memory['prop']:
            if k not in self.__dict__:
                setattr(self,k,(kwargs|A.memory['prop'])[k])
class B(A):
    def __init__(self,**kwargs):
        super().__init__(**kwargs)
        for k in B.memory['prop']:
            if k not in self.__dict__:
                setattr(self,k,(B.memory['prop'])[k])
class C(B):
    memory={'prop':{'c':2,'d':3}}
    def __init__(self,**kwargs):
        super().__init__(**kwargs)
        for k in C.memory['prop']:
            if k not in self.__dict__:
                setattr(self,k,(C.memory['prop'])[k])

But this means I have to do an extra job everytime I define a class (there's about a thousand of them)

I can also do something like this:

class A:
    memory={'prop':{'a':0,'b':1}}
    def __init__(self,clas,**kwargs):
       for k in kwargs|clas.memory['prop']:
            if k not in self.__dict__:
                setattr(self,k,(kwargs|clas.memory['prop'])[k])
class B(A):
    def __init__(self,clas,**kwargs):
        super().__init__(clas,**kwargs)
        for k in clas.memory['prop']:
            if k not in self.__dict__:
                setattr(self,k,(clas.memory['prop'])[k])
class C(B):
    memory={'prop':{'c':2,'d':3}}
    def __init__(self,clas,**kwargs):
        super().__init__(clas,**kwargs)
        for k in clas.memory['prop']:
            if k not in self.__dict__:
                setattr(self,k,(clas.memory['prop'])[k])

But this means I have to call the same class everytime I create an object object=C(C) which looks wrong.

What is the best way to do this?


Solution

  • You want prop to be a class attribute, but need a fresh instance of it for each subclass.

    An easy thing to do is to create a propin the __init_subclass__ method: this is a reserved method the language calls each time a subclass is created:

    from copy import deepcopy
    
    class A:
        memory={'prop':{'a':0,'b':1}}
        def __init__(self,**kwargs):
           cls = type(self) # it is better to be specific that we want to update a class property
           for k in kwargs|self.memory['prop']:
                if k not in self.__dict__:
                    setattr(self,k,(kwargs|cls.memory['prop'])[k])
    
        def __init_subclass__(cls, *args, **kw):
            super().__init_subclass__(cls, *args, **kw)
            # this will create a copy of A.memory for each subclass.
            # (__class__ is a special value that always points to THIS (A)
            # class, while "cls", received as a parameter, refers to the
            # subclass that is being created: 
            cls.memory = deepcopy(__class__.memory)
            # if you prefer each subclass to start with an empty memory:
            # cls.memory = {}
    
    class B(A):
        def __init__(self,**kwargs):
            super().__init__(**kwargs)
            # no need for any other code in the subclasses __init__ - 
            # the code on the superclass will store it on the apropriate 
            # dictionary for each class