2015-10-03 17 views
9

Ho uno script che gira su un ciclo infinito e aggiunge cose a un database e fa cose che non posso semplicemente interrompere a metà, quindi non posso semplicemente premere ctrl + C e fermarlo.Come fermare un loop infinito in modo sicuro in Python?

Voglio essere in grado di interrompere un ciclo while in qualche modo, ma lasciare che finisca l'ultima iterazione prima che si fermi.

Vorrei chiarire:

mio codice simile a questa:

while True: 
    does something 
    does more things 
    does more things 

voglio essere in grado di interrompere il ciclo while alla fine, o l'inizio, ma non tra il fare le cose perché sarebbe male

e non voglio che mi chieda dopo ogni iterazione se voglio continuare.

grazie per le grandi risposte, io sono super grato, ma la mia realizzazione non sembra funzionare:

def signal_handler(signal, frame): 
     global interrupted 
     interrupted = True 

class Crawler(): 
    def __init__(self): 
    # not relevent 

    def crawl(self): 
     interrupted = False 
     signal.signal(signal.SIGINT, signal_handler) 
     while True: 
      doing things 
      more things 

      if interrupted: 
       print("Exiting..") 
       break 

quando premo ctr + c il programma continua a andare soli ignorando me

+0

avete un modo per determinare nel vostro script che se il lavoro è completato (ultimo)! Se lo fai allora usalo in una condizione e usa una dichiarazione di rottura – praba230890

+0

Il lavoro in questo caso è un crawler web che può andare avanti indefinitamente. Voglio essere in grado di dirgli di smettere di strisciare, ma non solo di interromperlo nel mezzo di una pagina, per esempio. – davegri

+0

* lascia che finisca la sua ultima iterazione prima che si fermi *? Come fai a sapere se ha fatto il lavoro o no? –

risposta

9

Quello che dovete fare è prendere l'interrupt, impostare una bandiera dicendo che stati interrotti, ma poi continuare a lavorare fino a quando è il momento di controllare il flag (alla fine di ogni ciclo continuo). Poiché il costrutto try-except di python abbandonerà l'esecuzione corrente del ciclo, è necessario impostare un gestore di segnale appropriato; gestirà l'interrupt ma lascerà che python continui da dove era stato interrotto. Ecco come:

import signal 

import time # For the demo only 

def signal_handler(signal, frame): 
    global interrupted 
    interrupted = True 

signal.signal(signal.SIGINT, signal_handler) 


interrupted = False 
while True: 
    print("Working hard...") 
    time.sleep(3) 
    print("All done!") 

    if interrupted: 
     print("Gotta go") 
     break 

Note:

  1. Utilizzare questo dalla riga di comando. Nella console IDLE, calpesterà la gestione degli interrupt di IDLE.

  2. Una soluzione migliore sarebbe quella di "bloccare" KeyboardInterrupt per la durata del ciclo e sbloccarlo quando è il momento di eseguire il polling degli interrupt. Questa è una caratteristica di alcuni sapori Unix ma non tutti, quindi python does not support it (vedere la terza "Regola generale")

  3. L'OP vuole farlo all'interno di una classe. Ma la funzione di interruzione è invocata dal sistema di gestione del segnale, con due argomenti: il numero del segnale e un puntatore al frame dello stack-- nessun posto per un argomento self che dà accesso all'oggetto classe. Quindi il modo più semplice per impostare un flag è usare una variabile globale. È possibile impostare un puntatore sul contesto locale utilizzando le chiusure (ad esempio, definire dinamicamente il gestore di segnale in __init__(), ma sinceramente non mi preoccuperei a meno che un global non sia fuori questione a causa di multi-threading o altro.

Caveat: Se il processo è nel bel mezzo di una chiamata di sistema, la gestione di un segnale può interrompere la chiamata di sistema. Quindi questo potrebbe non essere sicuro per tutte le applicazioni. Alternative più sicure sarebbero (a) invece di fare affidamento sui segnali, usare una lettura non bloccante alla fine di ogni iterazione del ciclo (e digitare input invece di colpire^C); (b) utilizzare thread o comunicazioni tra processi per isolare il lavoratore dalla gestione del segnale; o (c) fare il lavoro di implementazione reale signal blocking, se si è su un sistema operativo che lo ha. Tutti sono OS-dipendenti in una certa misura, quindi lo lascerò.

+0

Ok, penso di aver capito, ma la mia funzione è in esecuzione in una classe, quindi devo definire signal_handler come funzione della classe? – davegri

+0

I gestori di segnale sono chiamati fuori dal normale flusso di controllo. Non devi _need_ per renderlo un metodo di classe, e non sono sicuro che funzionerà. Cerca nella documentazione [signal] (https://docs.python.org/3/library/signal.html) ... – alexis

+0

Risposta breve: usa solo una funzione di primo livello e una variabile globale. A meno che tu non sia multi-threaded ecc., Nel qual caso la vita diventa difficile ... – alexis

9

la logica di seguito vi aiuterà a fare questo,

import signal 
import sys 
import time 

run = True 

def signal_handler(signal, frame): 
    global run 
    print "exiting" 
    run = False 

signal.signal(signal.SIGINT, signal_handler) 
while run: 
    print "hi" 
    time.sleep(1) 
    # do anything 
    print "bye" 

durante l'esecuzione di questo, provate a premere CTRL + C

0

spero di seguito il codice che vi aiutano a:

#!/bin/python 

import sys 
import time 
import signal 

def cb_sigint_handler(signum, stack): 
    global is_interrupted 
    print "SIGINT received" 
    is_interrupted = True 

if __name__ == "__main__": 
    is_interrupted = False 
    signal.signal(signal.SIGINT, cb_sigint_handler) 
    while(1): 
     # do stuff here 
     print "processing..." 
     time.sleep(3) 
     if is_interrupted: 
      print "Exiting.." 
      # do clean up 
      sys.exit(0) 
0

Per chiarire la soluzione di @praba230890: la variabile interrupted non è stata definita nell'ambito corretto. È stato definito nella funzione crawl e il gestore non è riuscito a raggiungerlo come variabile globale, in base alla definizione del gestore nella radice del programma.