简体   繁体   中英

python - Accessing superclass's class attribute in super().__init__()

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?

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.

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)

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM