4

I have a running variable which is responsible for whether the program is running or not. There is also a loop that runs as long as running == True. This loop contains many functions, each of which takes, say, 1 second to complete.

Thus, if during the iteration of the loop the value of running is changed to False until the iteration is completely completed, the actions will be performed.

It is necessary for me that as soon as the value of running becomes False, the cycle is interrupted immediately (well, or almost immediately).

I have this solution:

running = True

while running:
    do_something1(time_length=1)
    if not running:
        break

    do_something2(time_length=1)
    if not running:
        break

    do_something3(time_length=1)
    if not running:
        break

    do_something4(time_length=1)
    if not running:
        break

    do_something5(time_length=1)
    if not running:
        break

    do_something6(time_length=1)
    if not running:
        break

    # etc.

However, this option looks very clumsy and takes up a lot of space. Is it possible not to prescribe a condition before each action, but to prescribe it, say, only at the beginning?

UPD 1: Due to the fact that I did not fully show the code, the answers do not quite suit me, as I understand it.

All variables and functions are inside the class. The code itself looks like this.

from threading import Thread


class SomeClass:
    def __init__(self):
        self.running = True

    def toggle_running_flag(self):
        # this function toggles self.running by user input
        self.running = not self.running
        if self.running:
            Thread(target=self.do_all_of_this).start()

    def do_something1(self):
        # do something
        pass

    def do_something2(self):
        # do something
        pass

    def do_something3(self):
        # do something
        pass

    def do_all_of_this(self):
        while self.running:
            self.do_something1()
            if not self.running:
                break

            self.do_something2()
            if not self.running:
                break

            self.do_something3()
        

3 Answers3

5

Instead of that flag variable, you could use an exception. Note that exceptions aren't just for "bad stuff", for example the StopIteration exception is how iterators signal that they're done.

Demo:

from contextlib import suppress

class StopRunning(Exception):
    pass

def do_something1():
    print('do_something1')
    raise StopRunning

def do_something2():
    print('do_something2')

with suppress(StopRunning):
    while True:
        do_something1()
        do_something2()

print('done')

Output:

do_something1
done

Try it online!

Pychopath
  • 1,560
  • 1
  • 8
  • Where do you check the Running condition? What if you want to run multiple time the do_something, where do you put the raise – Fredericka Feb 18 '22 at 00:54
  • @Fredericka I'd say "check" and "condition" are not what's happening. Instead, the exception is raised (by the code that previously set the `running` variable to `False`) and then caught/suppressed by the `suppress` context manager. – Pychopath Feb 18 '22 at 00:57
  • @Pychopath, i'm sorry. I have updated the question. –  Feb 18 '22 at 01:12
  • @Pychopath, i need the main loop to be inside a function and run with `threading.Thread()` Can I use the `with suppress(StopRunning)` construct inside a function that is also started with `threading.Thread()`? –  Feb 18 '22 at 01:19
  • @Mus1k I don't see why not. Though if you want that `running` variable not just as you originally showed but also for that toggling business, then I'd say go with Fredericka's method. Particularly now that you also showed that the `do_something`s are not just representing arbitrary code but actually are just function calls. – Pychopath Feb 18 '22 at 01:23
  • @Pychopath, understood, thanks! –  Feb 18 '22 at 01:26
1

Are the various do_somethings setting running = False? Relying on globals isn't a great pattern.

One alternative to updating a global flag is to have do_somethingN throw an exception to stop execution:

from do_things import StopRunning, do_something1, do_something2, # etc
try:
    while True:
        do_something1(time_length=1)
        do_something2(time_length=1)
        do_something3(time_length=1)
        do_something4(time_length=1)
        do_something5(time_length=1)
        do_something6(time_length=1)
except StopRunning:
    pass

Elsewhere:

# do_things.py
class StopRunning(Exception):
    pass

def do_something1(time_length):
    if time_length > 42:
        raise StopRunning

# etc
Jack Deeth
  • 3,062
  • 3
  • 24
  • 39
1

The is a way you can do that. You can create a function that will loop indefinitely between the functions you have to execute:

from itertools import cycle

class SomeClass:
    def __init__(self):
        self.running = True

    def toggle_running_flag(self):
        # this function toggles self.running by user input
        self.running = True
        Thread(target=self.do_all_of_this).start()

    def do_all_of_this(self):
        self.work = [self.do_something1, self.do_something2, self.do_something3]

        for func in cycle(self.work):
            func()
            if not self.running:
                return

After every iteration check if your program should still be running. If not return (Stop iteration)

Fredericka
  • 296
  • 1
  • 7
  • 1
    You could also use `for func in itertools.cycle(work)` instead of having both the while and for loop – Ted Klein Bergman Feb 18 '22 at 01:00
  • @Fredericka, but why use `cycle()`? Why can't i just use `for func in self.work`? –  Feb 18 '22 at 01:30
  • 1
    @Mus1k When you loop through a list it ends a the end of the list. In this case we want it to cycle indefinetly. The simple for loop would give (1,2,3), the cycle gives (1,2,3,1,2,3,1,2,3, etc) – Fredericka Feb 18 '22 at 01:40