Reputation: 167
I have a class Parent
with many instance properties, and I always pass a dict
to initialize an instance. Like this:
info = {
"name" : "Bob",
"col" : 5,
"line" : 10,
"type" : "Alien"
}
p = Parent(info)
And in __init__
method I don't want to write this.property_name = value
for each property cuz the code will be very long. For instance:
class Parent(object):
def __init__(self, kwargs):
this.name = kwargs.get("name")
this.col = kwargs.get("col")
this.line = kwargs.get("line")
this.type = kwargs.get("type")
So I want to use a function to iterate the dict
to set these instance properties. This is the function I wrote:
def initialize_properties(instance, names, kwargs):
for name in names:
setattr(instance, name, kwargs.get(name))
It seems that I need to store the property name list names
somewhere, and I decide to store it as a class attribute, because I want my class to be human friendly (when somebody reads the class definition he knows what instance properties this class has). So I changed my class definition as follows:
class Parent(object):
props = ("name", "col", "line", "type")
def __init__(self, kwargs):
initialize_properties(self, self.props, kwargs)
This works fine when inheritance is NOT considered. The problem occurs when I subclass Parent
:
class Child(Parent):
props = ("foo", "bar")
def __init__(self, kwargs):
super().__init__(kwargs)
initialize_properties(self, self.props, kwargs)
I want instances of Child
to inherit all the instance properties in superclass Parent
, with some child-specific instance properties as well.(This is why we use inheritance, isn't it?) So I overwrite the class attribute props
to define child-specific properties.
But it doesn't work.
info = {
"name" : "Bob",
"col" : 5,
"line" : 10,
"type" : "Alien",
"foo" : 5,
"bar" : 10
}
c = Child(info)
Instance c
only has c.foo
and c.bar
defined and set, while c.name
is not defined.
After some digging I found that when Parent.__init__(self, kwargs)
is called through the super()
function, the self
argument passed is of class Child
, so self.props
evaluates to Child.props
.
If I want to set the instance properties in Parent.props
, I have to explicitly use Parent.props
in Parent.__init__(self, kwargs)
, which is:
class Parent(object):
props = ("name", "col", "line", "type")
def __init__(self, kwargs):
initialize_properties(self, Parent.props, kwargs)
This will solve the problem, but I think it's not very "clean" because you have to hard-code the class name Parent
.
So my question is: Is there any way to detect the current class and access its class attributes, when you are calling a chain of super().__init__()
to initialize an subclass instance?
Upvotes: 5
Views: 4074
Reputation: 1
To answer you question
Is there any way to detect the current class and access its class attributes, when you are calling a chain of super().init()
In my opinion, no way.
However, you can use decorator to simplify your code
def add_props(names, cls=None):
if cls is None:
return lambda cls:add(names, cls)
for c in cls.__bases__:
names.extend(c.props)
names = list(set(names))
cls.props = names
return cls
class A:
props = ['a','b']
@add_props(names=['c'])
class B(A):
pass
output:
In [69]: B.props
Out[69]: ['a', 'b', 'c']
But, it must said that if you want human friendly, the best way i think is not using props
, just write code in your __init__
method.
class Root:
def __init__(self, a=None, b=None):
self.a = a
self.b = b
class Child(Root):
def __init__(self, c=None, d=None, **kwargs):
self.c = c
self.d = d
super().__init__(**kwargs)
You can also have convenience to pass dict as you like
data = {'a': 1}
c = Child(**data)
And it is not only friendly to human but also friendly to most editors.
Upvotes: 0
Reputation: 73450
You could implement a method that collects the props
from all supeclasses and use it in the __init__
:
class Parent(object):
props = ("name", "col", "line", "type")
def __init__(self, **kwargs):
def __init__(self, kwargs):
initialize_properties(self, self.get_props(), kwargs)
def get_props(self):
return sum((getattr(k, 'props', ()) for k in self.__class__.mro()), ())
Now, you can simply define subclasses the way you wanted. You wouldn't even have to override the constructor:
class Child(Parent):
props = ("foo", "bar")
That being said, you claim you want your classes human friendly. If a human reads your child class, they will be glad to read:
class Child(Parent):
props = 'foo', 'bar', 'name', 'col', 'line', 'type'
and have all the class' props
available in one place.
Upvotes: 3
Reputation: 368894
self.props
first references instance attribute, then class attribute, and parents' class attribute, ...
By defining Child.props
, self.props
refers Child.props
class attribute, stopping there, not searching in parent class.
How about make the Child.props
also include parents' props
?
class Child(Parent):
props = Parent.props + ("foo", "bar") # <---
def __init__(self, kwargs):
super().__init__(kwargs)
initialize_properties(self, self.props, kwargs)
Upvotes: 1