Python 3.6. keypress -> orderly exit during program execution

88 Views Asked by At

A question I have seen posed often, but have not yet found a solution that fits my problem. I would therefore appreciate some help to improve both my code and my understanding of Python.

I'm working on a large chunk of code that features Grammatical Evolution and runs in several nested (unavoidable at the moment) classes.

The base class (Genetic) handles the population, each member of the population is an instance of Individual class and is evaluated using the Algorithm class. To avoid over-complicating - a code that runs in several layers (with Spice simulated added to the mix) and runs for... quite a long time - hours at some times.

This is the reason why I started to search for a solution that would allow me to stop the execution in a way that would not mess everything up. If I use the usual ctrl+c option it simply destroys everything and forces me to restart the kernel and loose all the data.

What I'm thinking about is adding a sort of a monitor to the code that would allow me to enter a keypress sequence and then say "Ok, I see you want to finish, I'll simply complete the current evaluation loop and exit".

In my belief I cannot use a keyboard interrupt since this would immediately exit the code execution. More likely I would need a flag that would change if the key is detected....

Any help would be appreciated.

So to summarize my pseudoIdea would be like this:

for each generation:
  if exit.flag != true:
    for each individual:
      evaluate individual
  else:
    write result
    finish and exit

when key detected set exit.flag to true

Thank you!

2

There are 2 best solutions below

1
On BEST ANSWER

Found a solution using pynput

from genetic.mainCore import Genetic
from grammar.basicDict import params
from algorithm.core import Algorithm
from pynput import keyboard


def on_activate_h():
    print('<ctrl>+<alt>+h pressed')
    global gen
    gen.forcEnd = True

listener = keyboard.GlobalHotKeys({'<ctrl>+<alt>+h': on_activate_h})
listener.start()

algi = Algorithm()
gen = Genetic(algi)
gen.initGrammar(params)
gen.initPopulation()
gen.run()

listener.stop()

I modified the gen.run() method to include a forcEnd check. If it is set to True we skip the evaluation of next generation and exit. Tested this and it works, even when using an external simulator!

4
On

While it is possible to detect a keypress, you don't have to. Because it is possible to "capture" a Ctrl-c!

try:
    code_execution()
except KeyboardInterrupt:
    print("Ctrl-c was pressed, ready to exit")

Basically, the exception type KeyboardInterrupt is raised when Ctrl-c is pressed. And this is something you can catch and handle any way you see fit. You can read more about this exception in particular here and in case you're new to exception handling, this is a good starting point.

Bonus point - rare exceptions are faster than if statements.

From your comments I understand you can't wrap main execution and prevent it from stopping so I'm including another method. Signal trapping.
Ctrl-c sends the process SIGINT which we can configure to "trap" and handle it differently than normal (which is exiting).

import signal, os
import time

FLAG = False


def handler(signum, frame):
    """
    This function will execute each time Ctrl-c is pressed or SIGINT
    is sent to the process (such as `kill <pid>`).
    """
    global FLAG
    print('Signal handler called with signal', signum)
    FLAG = True

# Setup so we process signal SIGINT (includes Ctrl-C) with handler
signal.signal(signal.SIGINT, handler)


while True and not FLAG:
    time.sleep(0.5)
    print("Working hard!")
else:
    print("Done!")

You can read a little more about signal in Python in their docs. It is a standard method of inter-process communication, too, which you can utilize.