Home > Blockchain >  How to create nested __init__ functions in a inheritance chain?
How to create nested __init__ functions in a inheritance chain?

Time:10-08

I'm trying to implement inheritance system where child class will add a value to dictionary that he received from ansister.

For instance:

class First:
    specificator = {'first':1}
    inherited_specificator = {}
    def __init__(self, *args, **kwargs):
        ???

class Second(First):
    specificator = {'second':2}

class Third(Second):
    specificator = {'third':3}        

So i wish I can implement init method that Third class instance will have inherited_specificator = {'first':1, 'second':2, 'third':3}

What I've tried:

Create init method that will be calling parent init method that will be calling... and so on to collect specificators from all top level classes.

class First:
    specificator = {'first':1}
    def __init__(self, *args, **kwargs):
        super().__init__(args, kwargs)
        if not getattr(self, 'inherited_specificator ', None): setattr(self, 'inherited_specificator', {})
        self.inherited_specificator = {**self.inherited_specificator , **self.specificator}

However it didn't work for me for some reason, Third().inherited_specificator was equal to {'third':3}. Maybe I don't completely understand the super() method workstyle, but I wasn't able to find detailed info about my case.

Also I tried to create set_specificator function that will be called from init and where it should add current class specificator to inherited one, but the same problem appeared and all I got is {'third':3}.

If there's solution for my case? Thanks in advance.

Update:
I'm looking for solution without overwriting init method if possible

CodePudding user response:

You can iterate over the method resolution order (MRO), checking each class for a specificator attribute that can be used to update inherited_specificator. You'll probably want iterate in reverse order, so that child classes can override inherited values.

class First:
    specificator = {'first':1}

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.inherited_specificator = {}
        for s in reversed(type(self).mro()):
            try:
                self.inherited_specificator.update(s.specificator)
            except AttributeError:
                pass


class Second(First):
    specificator = {'second':2}


class Third(Second):
    specificator = {'third':3}


t = Third()
print(t.inherited_specificator)

This manual walking of the MRO is necessary because only one __init__ will be called per instance. That call will only see the specificator attribute of the actual instance passed to that call: there is no "intermediate" instances of First and Second on which First.__init__ will be called before your actual instance of Third is passed to First.__init__.

Alternatively, you can make inherited_specificator a class attribute (which makes more sense, IMO) instead of an instance attribute, and define it using __init_subclass__ (which gets called when the class is defined, not when it is instantiated).

class Specification:
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)

        cls.inherited_specificator = \
           getattr(cls, 'inherited_specificator', {})\ 
           | getattr(cls, 'specificator', {})


class First(Specification):
    specificator = {'first':1}


class Second(First):
    specificator = {'second':2}


class Third(Second):
    specificator = {'third':3}

The key here is that we (try to) access the inherited value of inherited_specificator before creating the new class's own value, which will be the combination of the inherited value and the value of the class's specificator attribute, if defined.

I'm using the new dict merge operator introduced in Python 3.9. In earlier versions, use something like

def __init_subclass__(cls, **kwargs):
    super().__init_subclass__(**kwargs)
    inherited = getattr(cls, 'inherited_specificator', {})
    d = cls.inherited_specificator = {}
    new_values = getattr(cls, 'specificator', {})
    d.update(**inherited, **new_values)

CodePudding user response:

It seems like this is what you want:

class First:
  def __init__(self):
    self.specificator = {'first':1}

class Second(First):
  def __init__(self):
    super().__init__()
    self.specificator['second'] = 2

class Third(Second):
  def __init__(self):
    super().__init__()
    self.specificator['third'] = 3

Then there is the output:

> f = First()
> s = Second()
> t = Third()
 
> f.specificator
{'first': 1}
> s.specificator
{'first': 1, 'second': 2}
> t.specificator
{'first': 1, 'second': 2, 'third': 3}

Note that this doesn't have to be done in this order; I simply did it for demonstration. You could still do:

> t = Third()
> t.specificator
{'first': 1, 'second': 2, 'third': 3}

CodePudding user response:

Including the inheritance of the parameters, and isolation in an inherited_specificator:

from copy import copy

class First:
    def __init__(self, *args, **kwargs):
        print(" > called First.__init__()")
        self.specificator = {"first": 1}
        print("specificator: %s" % self.specificator)
        print(" < end First.__init__()")

class Second(First):
    def __init__(self, *args, **kwargs):
        print(" > called Second.__init__()")
        super().__init__(self)
        self.inherited_specificator = copy(self.specificator)
        self.specificator["second"] = 2
        print("inherited_specificator: %s" % self.inherited_specificator)
        print("specificator: %s" % self.specificator)
        print(" < end Second.__init__()")

class Third(Second):
    def __init__(self, *args, **kwargs):
        print(" > called Third.__init__()")
        super().__init__(self)
        self.inherited_specificator.update(self.specificator)
        self.specificator = {"third": 3}
        print("inherited_specificator: %s" % self.inherited_specificator)
        print("specificator: %s" % self.specificator)
        print(" < end Third.__init__()")

obj = Third()

gives the output:

 > called Third.__init__()
 > called Second.__init__()
 > called First.__init__()
specificator: {'first': 1}
 < end First.__init__()
inherited_specificator: {'first': 1}
specificator: {'first': 1, 'second': 2}
 < end Second.__init__()
inherited_specificator: {'first': 1, 'second': 2}
specificator: {'third': 3}
 < end Third.__init__()
  • Related