Reputation: 3161
The context here is simple, I'm creating a base class that other developers will subclass it and they're supposed to implement the test
method.
Now I don't want them to call super().test()
on their implementation, how can I do this?
Bellow is what I've tried, but it does not work.
class Base():
def __init__(self):
self._test()
def _test(self):
try:
super()._test()
except AttributeError:
pass
self.test()
def test(self):
print('Base')
class One(Base):
def test(self):
print('One')
class Two(One):
def test(self):
print('Two!')
two = Two()
What I want is the code above to print
Base
One
Two!
without One or Two calling super().
I understand a solution is using __init_subclass_ and keep an array of methods, but that seems way too hacky
Upvotes: 1
Views: 181
Reputation: 149085
If you only want to force an automatic call of some methods from the base class, __init_subclass__
is indeed the way to go. You just override the required methods there. I would use something close to:
from functools import update_wrapper
from inspect import signature
class Base:
# will be overriden in subclasses
_SPECIAL_METHODS = ['test', 'test2']
def test(self):
print('Base')
def test2(self, i):
print(f'{i} in Base')
def __init_subclass__(cls):
def do_wrap(cls, method):
orig = getattr(cls, method)
# ignore methods not explicitely defined in cls
if not orig.__qualname__.startswith(cls.__qualname__): return
def wrapper(*args, **kwargs):
# first calls the method from Base with passed arguments
getattr(Base, method)(*args, **kwargs)
# then the one from the subclass
return orig(*args, **kwargs)
# keep the original signature, docstring, etc.
wrapper.__signature__ = signature(orig)
setattr(cls, method, update_wrapper(wrapper, orig))
for i in Base._SPECIAL_METHODS:
do_wrap(cls, i)
class One(Base):
def test(self):
# super().test()
print('One')
def test2(self, i):
print(f'{i} in One')
class Two(One):
def test2(self, j):
print('In Two:', j)
It gives:
>>> a = One()
>>> a.test()
Base
One
>>> a.test2(2)
2 in Base
2 in One
>>> b = Two()
>>> b.test()
Base
One
>>> b.test2(5)
5 in Base
In Two: 5
This example calls the base method with the arguments passed to the child class one, but this would be easy to adapt.
Upvotes: 2