Run a function until it first returns True, then do not run it again

I don't know of any way to have the decorator/wrapper not be called once a condition is reached, but it's pretty simple to turn it into a no-op once a condition is reached, which by your first comment is what you seem to want the code to do.

import time 
import random 

random.seed(12771)

threshold = 75

def run_until_first_true_reached(f):
    """
    Decorator that runs the function f until it first returns True. 
    After returning True once, it will stop running the wrapped function again.
    """

    def wrapper(*args, **kwargs):
        if not wrapper.reached:
            v = f(*args, **kwargs)
            # If f is False
            if not v:
                return v
            # If f is True
            else: 
                print("Threshold has been reached!")
                print("Comparison function above_threshold shouldn't be called any more.")
                wrapper.reached = True
        return None   #  ? or wahtever we want to return once the threshold is reached

    wrapper.reached = False
    return wrapper 

@run_until_first_true_reached
def above_threshold(value, threshold): 
    if value > threshold:
        print("above_threshold returns True")
        return True 
    else:   
        print("above_threshold returns False")
        return False

# Modelling the continuous stream of inputs 
for _ in range(100): 

    rand_val = random.randint(1,100)
    print("Rand. val: ", rand_val)

    above_threshold(rand_val, threshold)

    time.sleep(1)

Result:

Rand. val:  12
above_threshold returns False
Rand. val:  19
above_threshold returns False
Rand. val:  82
above_threshold returns True
Threshold has been reached!
Comparison function above_threshold shouldn't be called any more.
Rand. val:  92
Rand. val:  26
Rand. val:  18
Rand. val:  55
...

The interesting bit here is that you need somewhere to store the state...the fact that the threshold has been reached. The way I do this in decorators is to attach the state to the wrapper function.

I changed your logic a bit so that the wrapped function isn't called twice on each wrapper invocation. This was producing duplicate lines of output that prevented matching your requested output.


Simply use the idea, what you intended to: "switch on and off" the call of the wrapped function. For this purpose we can use a variable of the parent decorator function as a state flag:

import time 
import random 

random.seed(12771)

threshold = 75

def run_until_first_true_reached(f):
    """
    Decorator that runs the function f until it first returns True. 
    After returning True once, it will stop running the wrapped function again.
    """

    switch_on = True

    def wrapper(*args, **kwargs):
        nonlocal switch_on

        if switch_on:
            threshold_reached = f(*args, **kwargs)
            if threshold_reached:
                print("Threshold has been reached!")
                print("Comparison function above_threshold shouldn't be called any more.")
                switch_on = False

    return wrapper 

@run_until_first_true_reached
def above_threshold(value, threshold): 
    if value > threshold:
        print("above_threshold returns True")
        return True 
    else:   
        print("above_threshold returns False")
        return False

Output:

Rand. val:  12
above_threshold returns False
Rand. val:  19
above_threshold returns False
Rand. val:  82
above_threshold returns True
Threshold has been reached!
Comparison function above_threshold shouldn't be called any more.
Rand. val:  92
Rand. val:  26
Rand. val:  18
Rand. val:  55