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?
CodePudding user response:
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:
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 = __class__.memory.copy()
# 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 codde on the superclass will store it on the apropriate
# dictionary for each class
CodePudding user response:
You seem to be very excited to abuse the new dictionary union operator. Not only are you running it repeatedly in a loop every time you want to access a key, but it's obscuring your actual design, which is likely flawed:
- Apply
kwargs - Override them with class level defaults
Remember that that the union operator is non-commutative for dictionaries: the last right hand operator's value is always used in case of conflict. This is backwards from how such things are normally done (what's the point of kwargs at that point?), but let's ignore that for a minute.
You seem to want classes along your inheritance chain to be able to update __dict__ in __init__. That is fundamentally the opposite of inheritance, which hides the parent attribute. You can absorb the dictionary update into a method calls, which can use super to make sure the entire chain is handled:
class A:
def __init__(self, **kwargs):
self.__dict__.update(self.get_defaults())
self.__dict__.update(kwargs)
@classmethod
def set_defaults(cls):
return {'A': 1, 'B': 2}
class B(A):
pass
class C(B):
@classmethod
def get_defaults(cls):
return super().get_defaults() | {'b': 3, 'c': 4}
This way, you only ever need a single constructor. It also has the potential advantage of allowing you to generate the default values on the fly.
