Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pythonic way to create an idempotent method

Tags:

python

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

like image 727
VPfB Avatar asked Mar 02 '26 04:03

VPfB


1 Answers

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).

like image 176
Davis Herring Avatar answered Mar 03 '26 18:03

Davis Herring



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!