Reputation: 4012
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
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
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
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