venerdì 10 febbraio 2017

Python: interrompere un loop con la pressione di un tasto

Il problema che mi si è presentato era questo:

in un programma lanciato da riga di comando, senza bottoni o oggetti grafici, volevo che fosse consentito premere un pulsante per terminare un loop ma che però, se non viene premuto nulla, il loop resti in esecuzione.

In pratica l'operatore deve poter fermare un programma, tramite la pressione di un tasto, ma il programma dovrebbe continuare a essere in esecuzione: se non si preme nessun tasto il loop prosegue.
Ho cercato varie soluzioni, alcuni proponevano di usare la tecnica dei thread, altri funzioni complicate o la pressione del CTRL+C o break che però interrompono il programma immediatamente.

Alla fine la soluzione trovata è con la funzione getche() e con un timeout, trascorso il quale, il programma prosegue l'esecuzione del loop.

Il programma di esempio mostra a video il progressivo di numeri con a fianco la somma di tutti i precedenti, se l'operatore preme il tasto S il programma si ferma e mostra la somma finale.

Nell'esempio il loop, se non interrotto, si ferma dopo 50 numeri con un tempo di attesa di 0,2 secondi.

#----------------------------------------------------------------------+
# Programma       : press_key versione 1.01 del 10/02/2017             |
# Python versione : 3.6.0                                              |
#----------------------------------------------------------------------+

#-----------------------------------------
#           Import moduli 
#-----------------------------------------
import sys, time, msvcrt

#---- Definizione variabili globali - Costanti fisse ------
DEBUG   = 0
DELAY   = 0.2   # tempo attesa per consentire di premere tasto S per STOP
MAX_NUM = 51    # Numero massimo

#------------------------------------------------------------------------------
# readInput : Attende la pressione di un tasto per un tempo max = timeout
#------------------------------------------------------------------------------
# input : Scritta da stampare, default (vuoto), timeout
# return: carattere premuto
#------------------------------------------------------------------------------
def readInput( caption, default, timeout):

    start_time = time.time()

    if caption > "":
        print(caption)

    sys.stdout.flush()

    input = ''

    while True:

        if msvcrt.kbhit():

            byte_arr = msvcrt.getche()
            input = str.lower(byte_arr.decode('utf-8'))
            return input
            break

        if len(input) == 0 and (time.time() - start_time) > timeout:
            break

    if len(input) > 0:
        return input
    else:
        return default

#------------------------------------------------------------------------------
# INIZIO PROGRAMMA
#------------------------------------------------------------------------------
if __name__=='__main__':

   somma = 0
   i = 0

   print ("Premi S per fermare >>")

   for i in range(1, MAX_NUM):

       somma = somma + i
       print(("Numero = %3d"% (i)), "Somma = ", somma)

       a = readInput("", "", DELAY)
       if a == 's':
           print ("\npremuto s")
           print ("\nLa somma dei primi ", i, " numeri è ", somma)
           break
       elif a != '':

           print ("")   # per separare la riga con il carattere premuto

esempio di esecuzione:

  
Nota: il programma funziona da riga di comando ma, se eseguito in ambiente IDLE, non intercetta i caratteri.

Nessun commento:

Posta un commento