Let's have a class with a start method. It can be called many times, but the actual start code should be executed only once. Subsequent calls should do nothing. (a fancy word for it is idempotence)
Sounds easy:
class C:
def __init__(self):
self._started = False
def start(self):
if self._started:
return
# start code
print('start')
self._started = True
The problem is that the test must be repeated in all derived methods:
class C2(C):
def start(self):
if self._started: # don't forget!
return
# additional code
super().start()
# additional code
My solution #1:
I split the start into two functions:
class C:
def __init__(self):
self._started = False
def start(self):
# DO NOT OVERRIDE THIS ONE
if self._started:
return
self._start()
self._started = True
def _start(self):
# start code
print('start!')
class C2(C):
def _start(self):
# additional code
super()._start()
# additional code
The second attempt replaces the start method with a noop func after first call (precisely it creates a noop function in the instance that shadows the method in the class, but the effect is the same).
class C:
def start(self):
# start code
print('start!')
self.start = lambda: None
class C2(C):
def start(self):
# additional code
super().start()
# additional code
I'm not happy with my solutions, do you know a better one?
UPDATE: What I dislike:
#1: to modify start you must not touch start, but modify other function
#2: it is self-modyfing code
The idea that what is overridden in a derived class might not be an element of the public interface but some internal function that implements part of that interface is known as the non-virtual interface pattern. (When there are several such parts for one public method, it is more commonly known as “template method”.) The terminology comes from C++ (originally from Simula), where such a pattern can be enforced since only virtual functions can be overridden. Python relies on convention for (many) such things, of course, but the pattern is no less appropriate for it.
Your solution #1 is therefore unsurprising and reasonable; #2, on the other hand, relies on a little-used feature that doesn’t work with super or with manual up-calls like C.start(self).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With