python-3.xpython-3.9pynput

Stop a Program from Running using a Character Without pressing Enter key in Python 3.x


I have tried some solutions that I could get from the other threads here but still no luck. My issue is that the program can recognize the character I've entered but does not execute to stop the program from running in loop. Appreciate any help in advance. Thanks!

from pynput import keyboard
import sys
from time import sleep

def stop_program():
    sys.exit()

def run_program():
    while True:
        print('Program is running now... press \'x\' to stop')
        sleep(1)

def on_press(key):
    try:      
        if str(key) == "'x'":
            stop_program()        
       
    except AttributeError:
        pass

#non-blocking
listener = keyboard.Listener(
    on_press=on_press)

listener.start()

run_program()

Solution

  • In your program sys.exit() will not terminate the main process. It terminates a child thread spawned with keyboard.Listener.start. You can verify this by adding print statements above and below sys.exit():

    def stop_program():
        print("This will be printed")
        sys.exit()
        print("This will not be printed")
    

    From the docs:

    sys.exit([arg])
    Exit from Python. This is implemented by raising the SystemExit exception, so cleanup actions specified by finally clauses of try statements are honored, and it is possible to intercept the exit attempt at an outer level.
    ...
    Since exit() ultimately “only” raises an exception, it will only exit the process when called from the main thread, and the exception is not intercepted.

    1. Method:
      If you just want a shortcut to terminate your program, pressing Ctrl+C in your terminal should raise a KeyboardInterrupt and terminate the running process.

    2. Method:
      An alternative would be to call os._exit(1) instead of sys.exit(). This can terminate the process from the child thread and should work in your use case.

    3. Method:
      A more complex way would be to use threading.Event or something similar. This would allow the child thread to notify the main thread to terminate the program/break out of the main loop.

    Here is the relevant code:

    from threading import Event
    
    g_stop = Event()
    
    
    def stop_program():
        g_stop.set()
    
    
    def run_program():
        while not g_stop.is_set():
            print("Program is running now... press 'x' to stop")
            sleep(1)
    
        print("\nYou stopped the program.")
    

    This will send a "stop" Event which will break the main loop in run_program(). After that, you can print whatever message you like.

    g_stop is a global variable, which you might want to avoid. See this on how to pass it as an argument to the on_press(key) function.