0

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

3 Answers 3

1

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)
Sign up to request clarification or add additional context in comments.

5 Comments

That's it, thank you!
(Ignore my previous comment if you saw it before I deleted it.)
yes and googled the init_subclass, so i was actually waiting because it seemed like perspective way. I tried implement something like this but i was't able even to find this methods because googling 'python initialize class' always gives you novice tutorials. So there is no way using the subclass method>
OK, I think I have something I like. My first attempt was causing each subclass to update the same inherited value of inherited_specificator, rather than creating a class-local version using the inherited value.
Man, thank you a lot, worked like charm!
0

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}

1 Comment

Thanks, but if there possibility to do the same but with same init method?
0

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__()

5 Comments

Thanks, but if there possibility to do the same but with same init method?
assuming you want the specificator to be inherited from parent->[grand]children, AFAIK they need to be defined in each class' __init__(). Or do you mean fetching the grandparent's specificator from Third.__init__() ?
Yes, I thought it can be implemented like collecting grandparents specificators in a child init method
Played with super() a bit, but it can only invoke parent methods. I tried using a method, but the method will action on the current object/class, not the parent class.
Yes, same, luckily chepner found a mro ladder way

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.