web-dev-qa-db-fra.com

Entrée au clavier avec timeout en Python

Comment inviteriez-vous l'utilisateur à entrer des données, mais au bout de N secondes?

Google pointe un fil de discussion à ce sujet à l'adresse http://mail.python.org/pipermail/python-list/2006-January/533215.html mais cela ne semble pas fonctionner. L'instruction dans laquelle le délai d'attente survient, qu'il s'agisse d'un sys.input.readline ou timer.sleep (), j'obtiens toujours:

<type 'exceptions.TypeError'>: [raw_] entrée attendue au plus 1 argument, en a 2

qui en quelque sorte l'exception ne parvient pas à attraper.

34
pupeno

L'exemple auquel vous avez lié est faux et l'exception se produit lors de l'appel du gestionnaire d'alarmes au lieu de la lecture. Mieux vaut essayer ceci:

import signal
TIMEOUT = 5 # number of seconds your want for timeout

def interrupted(signum, frame):
    "called when read times out"
    print 'interrupted!'
signal.signal(signal.SIGALRM, interrupted)

def input():
    try:
            print 'You have 5 seconds to type in your stuff...'
            foo = raw_input()
            return foo
    except:
            # timeout
            return

# set alarm
signal.alarm(TIMEOUT)
s = input()
# disable the alarm after success
signal.alarm(0)
print 'You typed', s
25
user137673

L'utilisation d'un appel sélectif est plus courte et devrait être beaucoup plus portable

import sys, select

print "You have ten seconds to answer!"

i, o, e = select.select( [sys.stdin], [], [], 10 )

if (i):
  print "You said", sys.stdin.readline().strip()
else:
  print "You said nothing!"
76
Pontus

Pas une solution Python, mais ...

J'ai rencontré ce problème avec un script exécuté sous CentOS (Linux), et ce qui a bien fonctionné dans mon cas était simplement d'exécuter la commande Bash "read -t" dans un sous-processus. Un bidouillage dégoûtant brutal, je le sais, mais je me sens assez coupable à quel point cela a fonctionné que je voulais le partager avec tout le monde ici.

import subprocess
subprocess.call('read -t 30', Shell=True)

Tout ce dont j'avais besoin était quelque chose qui attendait 30 secondes à moins que la touche ENTER ne soit enfoncée. Cela a très bien fonctionné.

10
Locane

La réponse de Paul n'a pas bien fonctionné. Code modifié ci-dessous qui fonctionne pour moi

  • windows 7 x64

  • Vanilla CMD Shell (par exemple, pas git-bash ou autre shell non-M $)

    - rien msvcrt ne fonctionne dans git-bash semble-t-il.

  • python 3.6

(Je poste une nouvelle réponse, car éditer la réponse de Paul la changerait directement de python 2.x -> 3.x, ce qui semble trop pour une édition (py2 est toujours utilisé)

import sys, time, msvcrt

def readInput( caption, default, timeout = 5):

    start_time = time.time()
    sys.stdout.write('%s(%s):'%(caption, default))
    sys.stdout.flush()
    input = ''
    while True:
        if msvcrt.kbhit():
            byte_arr = msvcrt.getche()
            if ord(byte_arr) == 13: # enter_key
                break
            Elif ord(byte_arr) >= 32: #space_char
                input += "".join(map(chr,byte_arr))
        if len(input) == 0 and (time.time() - start_time) > timeout:
            print("timing out, using default value.")
            break

    print('')  # needed to move to next line
    if len(input) > 0:
        return input
    else:
        return default

# and some examples of usage
ans = readInput('Please type a name', 'john') 
print( 'The name is %s' % ans)
ans = readInput('Please enter a number', 10 ) 
print( 'The number is %s' % ans) 
5
mike

Et en voici un qui fonctionne sous Windows

Comme aucun de ces exemples ne fonctionnait sous Windows, j'ai donc fusionné différentes réponses StackOverflow pour obtenir les éléments suivants:


import threading, msvcrt
import sys

def readInput(caption, default, timeout = 5):
    class KeyboardThread(threading.Thread):
        def run(self):
            self.timedout = False
            self.input = ''
            while True:
                if msvcrt.kbhit():
                    chr = msvcrt.getche()
                    if ord(chr) == 13:
                        break
                    Elif ord(chr) >= 32:
                        self.input += chr
                if len(self.input) == 0 and self.timedout:
                    break    


    sys.stdout.write('%s(%s):'%(caption, default));
    result = default
    it = KeyboardThread()
    it.start()
    it.join(timeout)
    it.timedout = True
    if len(it.input) > 0:
        # wait for rest of input
        it.join()
        result = it.input
    print ''  # needed to move to next line
    return result

# and some examples of usage
ans = readInput('Please type a name', 'john') 
print 'The name is %s' % ans
ans = readInput('Please enter a number', 10 ) 
print 'The number is %s' % ans 
5
Paul

Le code suivant a fonctionné pour moi.

J'ai utilisé deux threads l'un pour obtenir le raw_Input et un autre pour attendre un moment spécifique . Si l'un des threads se ferme, le thread est terminé et renvoyé.

def _input(msg, q):
    ra = raw_input(msg)
    if ra:
        q.put(ra)
    else:
        q.put("None")
    return

def _slp(tm, q):
    time.sleep(tm)
    q.put("Timeout")
    return

def wait_for_input(msg="Press Enter to continue", time=10):
    q = Queue.Queue()
    th = threading.Thread(target=_input, args=(msg, q,))
    tt = threading.Thread(target=_slp, args=(time, q,))

    th.start()
    tt.start()
    ret = None
    while True:
        ret = q.get()
        if ret:
            th._Thread__stop()
            tt._Thread__stop()
            return ret
    return ret

print time.ctime()    
t= wait_for_input()
print "\nResponse :",t 
print time.ctime()
3
Mechatron

J'ai passé une bonne vingtaine de minutes dessus, alors j'ai pensé que ça valait le coup d'essayer de le dire ici. Il se base directement sur la réponse de user137673. J'ai trouvé très utile de faire quelque chose comme ça:

#! /usr/bin/env python

import signal

timeout = None

def main():
    inp = stdinWait("You have 5 seconds to type text and press <Enter>... ", "[no text]", 5, "Aw man! You ran out of time!!")
    if not timeout:
        print "You entered", inp
    else:
        print "You didn't enter anything because I'm on a tight schedule!"

def stdinWait(text, default, time, timeoutDisplay = None, **kwargs):
    signal.signal(signal.SIGALRM, interrupt)
    signal.alarm(time) # sets timeout
    global timeout
    try:
        inp = raw_input(text)
        signal.alarm(0)
        timeout = False
    except (KeyboardInterrupt):
        printInterrupt = kwargs.get("printInterrupt", True)
        if printInterrupt:
            print "Keyboard interrupt"
        timeout = True # Do this so you don't mistakenly get input when there is none
        inp = default
    except:
        timeout = True
        if not timeoutDisplay is None:
            print timeoutDisplay
        signal.alarm(0)
        inp = default
    return inp

def interrupt(signum, frame):
    raise Exception("")

if __== "__main__":
    main()
2
dylnmc

Analogue à Locane pour Windows:

import subprocess  
subprocess.call('timeout /T 30')
2

Voici une solution portable et simple Python 3 utilisant des threads .C'est la seule qui a fonctionné pour moi tout en étant multi-plateforme.

Les autres choses que j'ai essayées avaient toutes des problèmes:

  • Utilisation de signal.SIGALRM: ne fonctionne pas sous Windows
  • Utilisation de select call: ne fonctionne pas sous Windows
  • Utilisation du processus d'arrêt forcé d'un processus (au lieu d'un thread): stdin ne peut pas être utilisé dans un nouveau processus (stdin est à fermeture automatique)
  • La redirection stdin vers StringIO et l'écriture directe sur stdin: écrit toujours sur la précédente stdin si input () a déjà été appelé (voir https://stackoverflow.com/a/15055639/9624704 )
    from threading import Thread
    class myClass:
        _input = None

        def __init__(self):
            get_input_thread = Thread(target=self.get_input)
            get_input_thread.daemon = True  # Otherwise the thread won't be terminated when the main program terminates.
            get_input_thread.start()
            get_input_thread.join(timeout=20)

            if myClass._input is None:
                print("No input was given within 20 seconds")
            else:
                print("Input given was: {}".format(myClass._input))


        @classmethod
        def get_input(cls):
            cls._input = input("")
            return
1
jorisv92

Puisque cette question semble servir de cible dupliquée, ici le lien vers ma réponse acceptée dans une question dupliquée.

Caractéristiques

  • Indépendant de la plate-forme (Unix/Windows).
  • StdLib uniquement, pas de dépendances externes.
  • Fils seulement, pas de sous-processus.
  • Interruption immédiate à l'expiration du délai.
  • Arrêt propre du prompteur à la fin du temps imparti.
  • Entrées illimitées possibles pendant une période donnée.
  • Classe PromptManager facilement extensible.
  • Le programme peut reprendre après expiration du délai, plusieurs exécutions d'instances de Prompter possibles sans redémarrage du programme.
0
Darkonaut

ma solution multiplateforme

def input_process(stdin_fd, sq, str):
    sys.stdin = os.fdopen(stdin_fd)
    try:
        inp = input (str)
        sq.put (True)
    except:
        sq.put (False)

def input_in_time (str, max_time_sec):
    sq = multiprocessing.Queue()
    p = multiprocessing.Process(target=input_process, args=( sys.stdin.fileno(), sq, str))
    p.start()
    t = time.time()
    inp = False
    while True:
        if not sq.empty():
            inp = sq.get()
            break
        if time.time() - t > max_time_sec:
            break
    p.terminate()
    sys.stdin = os.fdopen( sys.stdin.fileno() )
    return inp
0
iperov