Python inheritance Best way to merge info with parents

Question:

Imagine we have a class Animal with a class attribute info, which is a dictionary. There is a class Frog which inherits from Animal and also has a class attribute info. But the attribute info of the Frog class must also include the Animal info.

class Animal:
    info = {'one':1, 'two':2}

class Frog(Animal):
    info = {'three':3}

assert 'three' in Frog.info

I can use @property

class Frog(Animal):
    _info = {'three':3}
    @property
    def info(self):
         return self._info | Frog.info

But this is just fragile. Do the large Python library includes any Class that doesn’t overwrite the Class attributes?

Asked By: Mert Mint

||

Answers:

You can refer to the parent’s attribute in the child.

class Frog(Animal):
    info = Animal.info + {'three': 3}
Answered By: Barmar

If you want the info values to be automatically merged by subclasses, you will need to use a metaclass to achieve this.

The metaclass is code which can modify the class definition, so it can look at the value of info on the parent class and the value of info in the sub-class as it is being defined … and it can merge them and set the result of that as the value of info in the resulting sub-class.

It would look like this:

class MergeInfoMetaclass(type):
    def __new__(metacls, name, bases, attrs):
        base_info = {}
        for base in bases:
            try:
                base_info.update(base.info)
            except AttributeError:
                continue

        try:
            info = attrs["info"]
        except KeyError:
            pass
        else:
            attrs["info"] = base_info | info
        
        return super().__new__(metacls, name, bases, attrs)


class Animal(metaclass=MergeInfoMetaclass):
    info = {'one': 1, 'two': 2}


class Frog(Animal):
    info = {'three': 3}


class GoldenTreeFrog(Frog):
    info = {'four': 4}


assert Animal.info == {'one': 1, 'two': 2}

assert Frog.info == {'one': 1, 'two': 2, 'three': 3}

assert GoldenTreeFrog.info == {'one': 1, 'two': 2, 'three': 3, 'four': 4}

You can find more info about Python metaclasses here https://realpython.com/python-metaclasses/#custom-metaclasses

Basically, when Python is reading your class definition, like:

class Animal(metaclass=MergeInfoMetaclass):
    info = {'one': 1, 'two': 2}

Python takes the details of the new class (the name, base classes and class attrs, which include the methods too) and passes them to the metaclass. At this point the Animal class does not exist yet. The metaclass is responsible for creating the new class object (meaning the class itself and not an instance of the class).

Answered By: Anentropic