how to make child class call parent class __init__ automatically?

Question:

i had a class called CacheObject,and many class extend from it.

now i need to add something common on all classes from this class so i write this

class CacheObject(object):

    def __init__(self):
        self.updatedict = dict() 

but the child class didn’t obtain the updatedict attribute.i know calling super init function was optional in python,but is there an easy way to force all of them to add the init rather than walk all the classes and modify them one by one?

Asked By: user2003548

||

Answers:

You can add a decorator to your classes :

def my_decorator(cls):
    old_init = cls.__init__
    def new_init(self):
        self.updatedict = dict()
        old_init(self)
    cls.__init__ = new_init
    return cls

@my_decorator
class SubClass(CacheObject):
    pass

if you want to add the decorators to all the subclasses automatically, use a metaclass:

class myMeta(type):
    def __new__(cls, name, parents, dct):
        return my_decorator(super().__new__(cls, name, parents, dct))

class CacheObject(object, metaclass=myMeta):
    pass
Answered By: Arsh Singh

You can override __new__. As long as your base classes doesn’t override __new__ without calling super().__new__, then you’ll be fine.

class CacheObject(object):
    def __new__(cls, *args, **kwargs):
        instance = super().__new__(cls, *args, **kwargs)
        instance.updatedict = {}
        return instance

class Foo(CacheObject):
    def __init__(self):
        pass

However, as some commenters said, the motivation for this seems a little shady. You should perhaps just add the super calls instead.

Answered By: Lie Ryan

This isn’t what you asked for, but how about making updatedict a property, so that it doesn’t need to be set in __init__:

class CacheObject(object):
    @property
    def updatedict(self):
        try:
            return self._updatedict
        except AttributeError:
            self._updatedict = dict()
            return self._updatedict

Hopefully this achieves the real goal, that you don’t want to have to touch every subclass (other than to make sure none uses an attribute called updatedict for something else, of course).

There are some odd gotchas, though, because it is different from setting updatedict in __init__ as in your question. For example, the content of CacheObject().__dict__ is different. It has no key updatedict because I’ve put that key in the class, not in each instance.

Answered By: Steve Jessop

I suggest a non-code fix:

Document that super().__init__() should be called by your subclasses before they use any other methods defined in it.

This is not an uncommon restriction. See, for instance, the documentation for threading.Thread in the standard library, which says:

If the subclass overrides the constructor, it must make sure to invoke the base class constructor (Thread.__init__()) before doing anything else to the thread.

There are probably many other examples, I just happened to have that doc page open.

Answered By: Blckknght

I was in a situation where I wanted classes to always call their base classes’ constructor in order before they call their own. The following is Python3 code that should do what you want:

  class meta(type):
    def __init__(cls,name,bases,dct):
      def auto__call__init__(self, *a, **kw):
        for base in cls.__bases__:
          base.__init__(self, *a, **kw)
        cls.__init__child_(self, *a, **kw)
      cls.__init__child_ = cls.__init__
      cls.__init__ = auto__call__init__

  class A(metaclass=meta):
    def __init__(self):
      print("Parent")

  class B(A):
    def __init__(self):
      print("Child")

To illustrate, it will behave as follows:

>>> B()
Parent
Child
<__main__.B object at 0x000001F8EF251F28>
>>> A()
Parent
<__main__.A object at 0x000001F8EF2BB2B0>
Answered By: james

Regardless of motivation, another option is to use __init_subclass__() (Python 3.6+) to get this kind of behavior. (For example, I’m using it because I want users not familiar with the intricacies of Python to be able to inherit from a class to create specific engineering models, and I’m trying to keep the structure of the class they have to define very basic.)

In the case of your example,

class CacheObject:
    def __init__(self) -> None:
        self.updatedict = dict()

    def __init_subclass__(cls) -> None:        
        orig_init = cls.__init__

        @wraps(orig_init)
        def __init__(self, *args, **kwargs):
            orig_init(self, *args, **kwargs)
            super(self.__class__, self).__init__()
        
        cls.__init__ = __init__

What this does is any class that subclasses CacheObject will now, when created, have its __init__ function wrapped by the parent class—we’re replacing it with a new function that calls the original, and then calls super() (the parent’s) __init__ function. So now, even if the child class overrides the parent __init__, at the instance’s creation time, its __init__ is then wrapped by a function that calls it and then calls its parent.

Answered By: sequoia
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.