10

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?

4
  • are you looking for a solution which should work correctly with multiple inheritence? if so, you must call super in any case. If the subclasses don't define there own __init__, base-class's __init__ is called automaticallye. Else, they must include a call to super().__init__. Commented Sep 26, 2013 at 13:34
  • 3
    "i know calling super init function was optional in python" -- not if you want the base class to have the freedom to run initialization code, it isn't. Commented Sep 26, 2013 at 13:36
  • if i reverse the control the subclass still need to be modified.all subclass init must be rename. Commented Sep 26, 2013 at 13:49
  • 1
    How many classes do you have inheriting from CacheObject that you need to add the super line to? Although I like the concept behind this question, it seems like the motivation is a bit shady. Plus you only have to add the super once (to each class). That doesn't seem particularly onerous to me. And you know what they say - explicit is better than implicit. Commented Sep 26, 2013 at 14:07

6 Answers 6

10

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

3 Comments

Thank you. I have a question when I'm using this. Can you please take a look? stackoverflow.com/q/64753383/6699433
Hey @klutt, I checked out the answers over there: both are very good and accurate. My code does indeed make a bit of a mess, it will probably have to be tweaked a bit to prevent constructors from being called over and over again.
Welcome back. I was not sure that you were still around. But you should probably edit this answer to let others know that it is not as good as it looks.
6

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.

Comments

4

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.

4 Comments

This is the cleanest answer in this thread.
IsCacheObject.__new__ called before the computation of Foo.__init__ of after?
__new__ creates an object, __init__ is then called to initialise the object that was just created.
@joeyagreco indeed! That this is the cleanest answer shows that constructors and initializers are badly designed in Python.
3

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.

1 Comment

If you used cached_property, you can just return empty dict. The moment it's accessed, it'll set the cache and then act just like regular attribute.
1

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.

Comments

-2

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

1 Comment

All instances of CacheObject will share the same dictionary - that's definitely not what the OP wants. He wants an instance attribute on each instance. Also, python3 doesn't use __metaclass__ anymore, it's class CacheObject(object, metaclass=myMeta) now.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.