Matthew Lund
Matthew Lund

Reputation: 4012

Possible to prevent init from being called?

I'm editing the original question because we're all focusing on SHOULD you ever want to do this. My question is simply CAN I do this and HOW (understanding that there may be several solutions). So I'm just going to leave the actual question and cut out the background.

Suppose I have a base class and a child class. Is there anything I can do in the base class to prevent __init__ from being called on the child class - or at least throw an exception or even log if __init__ exists or is called on the child class? I do want the __init__ method to be called on the parent class.

Edit/Conclusion - After exploring the options presented in the answers, I decided that doing this would be bad style. I will solve my problem a different way. Nonetheless, hopefully the answers below are helpful in case someone else wants to do this.

Upvotes: 15

Views: 8739

Answers (3)

TKK
TKK

Reputation: 369

Most of these answers are outdated.

This can be easily done since python 3.6. It was defined in PEP487.

class Base:
    def __init_subclass__(cls):
        if Base.__init__ is not cls.__init__:
            raise Exception(f'Do not override {cls}.__init__')

class Good(Base):
    pass

class Bad(Base):
    def __init__(self):
        pass

Good() # No problem
Bad()  # Raises Exception

Upvotes: 5

jsbueno
jsbueno

Reputation: 110271

" Whether or not I should or need to do it is a separate discussion :)"

Please, keep that in mind.

But it can be done - when a class is instantiated, not only the syntax is just like a method call - with the class object name followed y a parenthesis - the class itself (which is a Python object), is called - as a callable object.

Calling an object in Python invokes the __call__ magic method in its class. Therefore, instantiating a class, invokes the __call__ method on its metaclass.

What is inside this __call__ method in the standard metaclass (which is "type") is roughly equivalent to:

def __call__(cls, *args, **kw):
    self = cls.__new__(cls, *args, **kw)
    cls.__init__(self, *args, **kw)
    return self

So, if you write a metaclass, overriding __call__ and suppress the call to __init__ in these, it won't be called at all:

class Meta(type):
    def __call__(cls, *args, **kw):
        return cls.__new__(cls, *args, **kw)

class NoInit(object):
    __metaclass__ = Meta
    def __init__(self):
        print "Hello!"

NoInit()

If you want just to avoid that sublcasses have __init__ instead of not calling it, you can do a much simpler metaclass that would just raise an exception at class instantiation time:

class Meta(type):
    def __new__(metacls, name, bases, dct):
         if "__init__" in dct:
              raise NameError("Classes in this hierarchy should not have an __init__ method")
         return type.__new__(metacls, name, bases, dct)

Upvotes: 19

JBernardo
JBernardo

Reputation: 33397

That's quite doable, but I don't think you should. Tell the users how to use your class and they should obey. Also, if someone is subclassing he should know how to call the parent's initialization method.

As a proof of concept, here's how it can be done with metaclasses (Python 2.x syntax):

>>> class WhoMovedMyInit(object):
        class __metaclass__(type):
            def __init__(self, *args, **kw):
                super(type,self).__init__(*args, **kw)
                if self.__init__ is not WhoMovedMyInit.__init__:
                    raise Exception('Dude, I told not to override my __init__')


>>> class IAmOk(WhoMovedMyInit):
        pass

>>> class Lol(WhoMovedMyInit):
        def __init__(self):
            pass

Traceback (most recent call last):
  File "<pyshell#35>", line 1, in <module>
    class Lol(WhoMovedMyInit):
  File "<pyshell#31>", line 6, in __init__
    raise Exception('Dude, I told not to override my __init__')
Exception: Dude, I told not to override my __init__

You can also replace the subclass __init__ method to one which warns the user or raises an error on "runtime".

Upvotes: 11

Related Questions