103 votes

Entrée au clavier avec délai d'attente?

Comment demanderiez-vous à l'utilisateur de saisir des données mais en arrêtant après N secondes ?

Google pointe vers un fil de discussion par e-mail à ce sujet sur http://mail.python.org/pipermail/python-list/2006-January/533215.html mais cela ne semble pas fonctionner. Peu importe la déclaration dans laquelle le délai d'attente se produit, que ce soit un sys.input.readline ou un timer.sleep(), j'obtiens toujours :

: [raw_]input expected at most 1 arguments, got 2

ce que sauf erreur ne parvient pas à capturer.

2voto

Harison Silva Points 80

Vous pouvez utiliser en Python >= 3.4 la bibliothèque inputimeout. Licence MIT.

$ pip install inputimeout

from inputimeout import inputimeout, TimeoutOccurred
try:
    something = inputimeout(prompt='>>', timeout=5)
except TimeoutOccurred:
    something = 'something'
print(something)

1voto

Markus von Broady Points 247

C'est ainsi que j'ai abordé ce problème. Je ne l'ai pas testé minutieusement et je ne suis pas sûr qu'il n'ait pas de problèmes importants, mais étant donné que d'autres solutions sont également loin d'être parfaites, j'ai décidé de partager :

import sys
import subprocess

def switch():
    if len(sys.argv) == 1:
        main()
    elif sys.argv[1] == "inp":
        print(input(''))
    else:
        print("Mauvais arguments :", sys.argv[1:])

def main():
    passw = input_timed('Vous avez 10 secondes pour entrer le mot de passe :', timeout=10)
    if passw is None:
        print("Temps écoulé ! Vous explosez !")
    elif passw == "MotDePasseChmatom":
        print("C-comment as-tu su, hacker ?")
    else:
        print("Je te laisse la vie sauve car tu as au moins essayé")

def input_timed(*args, timeout, **kwargs):
    """
    Imprimer un message et attendre la saisie de l'utilisateur - retourner None si le temps est écoulé
    :param args: arguments positionnels passés à print()
    :param timeout: nombre de secondes à attendre avant de renvoyer None
    :param kwargs: arguments par mots-clés passés à print()
    :return: saisie de l'utilisateur ou None si le temps est écoulé
    """
    print(*args, **kwargs)
    try:
        out: bytes = subprocess.run(["python", sys.argv[0], "inp"], capture_output=True, timeout=timeout).stdout
    except subprocess.TimeoutExpired:
        return None
    return out.decode('utf8').splitlines()[0]

switch()

1voto

Dipesh Paul Points 451
import datetime, time

def custom_time_input(msg, seconds):
    try:
        print(msg)
        # temps actuel en secondes
        current_time = datetime.datetime.now()
        time_after = current_time + datetime.timedelta(seconds=seconds)
        while datetime.datetime.now() < time_after:
            print("Temps restant: ", end="")
            print(time_after - datetime.datetime.now(), end="\r")
            time.sleep(1)
        print("\n")
        return True
    except KeyboardInterrupt:
        return False

res = custom_time_input("Si vous voulez créer un nouveau fichier de configuration, APPUYEZ SUR CTRL+C dans les 20 secondes!", 20)
if res:
    pass # rien n'a changé
else:
    pass # faire quelque chose car l'utilisateur a appuyé sur ctrl+c

0voto

taper Points 158

Solution inspirée de la réponse de iperov qui, espérons-le, est un peu plus propre:

import multiprocessing
import sys

def input_with_timeout(prompt, timeout=None):
    """Demande à l'utilisateur d'entrer un code dans la ligne de commande."""
    queue = multiprocessing.Queue()
    process = multiprocessing.Process(
        _input_with_timeout_process, args=(sys.stdin.fileno(), queue, prompt),
    )
    process.start()
    try:
        process.join(timeout)
        if process.is_alive():
            raise ValueError("Temps écoulé en attendant une entrée.")
        return queue.get()
    finally:
        process.terminate()

def _input_with_timeout_process(stdin_file_descriptor, queue, prompt):
    sys.stdin = os.fdopen(stdin_file_descriptor)
    queue.put(input(prompt))

0voto

Anakhand Points 1744

Ceci est une approche multiplateforme en Python 3.8+ (bien qu'elle puisse être adaptée à Python 3.6+) qui n'utilise que threading (donc pas de multiprocessing ou d'appels à des utilitaires shell). Elle est destinée à l'exécution de scripts à partir de la ligne de commande et n'est pas très adaptée à une utilisation dynamique.

Vous pouvez envelopper la fonction intégrée input comme suit. Dans ce cas, je redéfinis le nom intégré input en tant que wrapper, car cette implémentation exige que tous les appels à input soient routés à travers celui-ci. (Avertissement: c'est pourquoi ce n'est probablement pas une très bonne idée, juste une différente, pour s'amuser.)

import atexit
import builtins
import queue
import threading

def _make_input_func():
    prompt_queue = queue.Queue(maxsize=1)
    input_queue = queue.Queue(maxsize=1)

    def get_input():
        while (prompt := prompt_queue.get()) != GeneratorExit:
            inp = builtins.input(prompt)
            input_queue.put(inp)
            prompt_queue.task_done()

    input_thread = threading.Thread(target=get_input, daemon=True)

    last_call_timed_out = False

    def input_func(prompt=None, timeout=None):
        """Imite :function:`builtins.input`, avec un délai facultatif

        :param prompt: chaîne à passer à builtins.input
        :param timeout: durée d'attente de l'entrée en secondes; None signifie indéfiniment

        :return: l'entrée reçue si le délai n'est pas dépassé, sinon None
        """
        nonlocal last_call_timed_out

        if not last_call_timed_out:
            prompt_queue.put(prompt, block=False)
        else:
            print(prompt, end='', flush=True)

        try:
            result = input_queue.get(timeout=timeout)
            last_call_timed_out = False
            return result
        except queue.Empty:
            print(flush=True) # facultatif: terminer la ligne de l'invite si aucune entrée n'est reçue
            last_call_timed_out = True
            return None

    input_thread.start()
    return input_func

input = _make_input_func()
del _make_input_func

(J'ai défini la configuration dans la fonction unique _make_input_func pour masquer les variables "statiques" de input dans sa fermeture, afin d'éviter de polluer l'espace de noms global.)

L'idée ici est de créer un thread séparé qui gère tous les appels à builtins.input, et de faire en sorte que le wrapper input gère le délai. Étant donné qu'un appel à builtins.input bloque toujours jusqu'à ce qu'il y ait une entrée, lorsque le délai expire, le thread spécial attend toujours une entrée, mais le wrapper input retourne (avec None). Au prochain appel, si le dernier appel a expiré, il n'a pas besoin de rappeler builtins.input (car le thread d'entrée attend déjà une entrée), il imprime simplement l'invite, puis attend que ledit thread retourne une entrée, comme toujours.

Après avoir défini ce qui précède, essayez d'exécuter le script suivant:

import time

if __name__ == '__main__':
    timeout = 2
    start_t = time.monotonic()
    if (inp := input(f"Entrez quelque chose (vous avez {timeout} secondes): ", timeout)) is not None:
        print("Entrée reçue:", repr(inp))
    else:
        end_t = time.monotonic()
        print(f"Délai dépassé après {end_t - start_t} secondes")

    inp = input("Entrez quelque chose d'autre (j'attendrai cette fois-ci): ")
    print("Entrée reçue:", repr(inp))

    input(f"Dernière chance de dire quelque chose (vous avez {timeout} secondes): ", timeout)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X