web-dev-qa-db-fra.com

Comment arrêter une boucle infinie en toute sécurité en Python?

J'ai un script qui fonctionne sur une boucle infinie et ajoute des éléments à une base de données et effectue des tâches que je ne peux pas arrêter juste au milieu, donc je ne peux pas appuyer simplement sur Ctrl + C et l'arrêter.

Je veux pouvoir en quelque sorte arrêter une boucle while, mais la laisser terminer sa dernière itération avant qu'elle ne s'arrête.

Permettez-moi de préciser:

mon code ressemble à ceci:

while True:
    does something
    does more things
    does more things

Je veux pouvoir interrompre la boucle while à la fin ou au début, mais pas entre les choses parce que ce serait mauvais.

et je ne veux pas qu'il me demande après chaque itération si je veux continuer.

merci pour les bonnes réponses, je suis très reconnaissant, mais mon implémentation ne semble pas fonctionner:

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

quand je presse ctr + c le programme ne cesse de m'ignorer

10
davegri

Ce que vous devez faire, c'est intercepter l'interruption, définir un drapeau indiquant que vous avez été interrompu, puis continuer à travailler jusqu'à ce qu'il soit temps de vérifier le drapeau (à la fin de chaque boucle). Étant donné que la construction try-except de python abandonnera l'exécution actuelle de la boucle, vous devez configurer un gestionnaire de signal approprié; il gérera l’interruption mais laissera python continuer là où il s’est arrêté. Voici comment:

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

Remarques: 

  1. Utilisez ceci à partir de la ligne de commande. Dans la console IDLE, le traitement des interruptions par IDLE sera piétiné.

  2. Une meilleure solution consisterait à "bloquer" KeyboardInterrupt pendant la durée de la boucle et à la débloquer au moment d'interroger les interruptions. Ceci est une caractéristique de certains types Unix mais pas de tous, d’où python ne le supporte pas (voir la troisième "règle générale")

  3. Le PO veut faire cela à l'intérieur d'une classe. Mais la fonction d'interruption est appelée par le système de traitement du signal, avec deux arguments: le numéro du signal et un pointeur sur le cadre de la pile - aucune place pour un argument self donnant accès à l'objet de classe. Par conséquent, le moyen le plus simple de définir un indicateur consiste à utiliser une variable globale. Vous pouvez configurer un pointeur sur le contexte local à l’aide de fermetures (c’est-à-dire définir le gestionnaire de signaux de manière dynamique dans __init__(), mais franchement, cela ne me dérangerait pas que si un global est hors de question en raison du multi-threading ou autre.

Caveat: Si votre processus est en cours d'appel système, le traitement d'un signal peut interrompre l'appel système. Donc, cela peut ne pas être sûr pour toutes les applications. Des alternatives plus sûres seraient: (a) au lieu de compter sur des signaux, utilisez une lecture non bloquante à la fin de chaque itération de boucle (et tapez entrée au lieu de frapper ^ C); (b) utiliser des threads ou une communication interprocessus pour isoler le travailleur de la gestion du signal; ou (c) effectuez le travail de mise en œuvre du blocage réel du signal , si vous êtes sur un système d'exploitation qui en est équipé. Tous dépendent de l'OS dans une certaine mesure, alors je vais en rester là.

13
alexis

la logique ci-dessous vous aidera à le faire,

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"

tout en exécutant ceci, essayez d'appuyer sur CTRL + C

10
praba230890

Pour clarifier la solution de @ praba230890: La variable interrupted n'a pas été définie dans la portée correcte. Il a été défini dans la fonction crawl et le gestionnaire ne peut pas l'atteindre en tant que variable globale, selon la définition du gestionnaire à la racine du programme.

0
Anthony Scemama

J'espère que le code ci-dessous vous aiderait:

#!/bin/python

import sys
import time
import signal

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

if __== "__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
pdave