Timeout in Python input function

Asked

Viewed 2,238 times

3

teste = input('Olá qual o seu nome?')

How to run something if the user takes longer than a certain amount of time to respond to input?

Example:

  • Write on screen Você demorou demais.

2 answers

7


The function signal.alarm is only available for Unix environment.

You can use the module signal. First, we have defined a function that will treat the case of the user being inactive and their time expiring. To make it simple, I will just fire an exception:

def timeout():
    raise Exception('Seu tempo acabou!')

Finally, the magic. With the module signal, we define a time signal in which the function timeout will be responsible for processing. We do this simply with:

signal.signal(signal.SIGALRM, timeout)

And then we do:

try:
    signal.alarm(5)
    name = input('Qual é o seu nome? ')
    signal.alarm(0)
    print('Seja bem-vindo,', name)
except Exception as e:
    print(e)

That is, we set the alarm time of our signal to 5 seconds, that is, from this moment running 5 seconds, the function timeout will be executed; we try to read the user name and, if successful, we cancel our signal by setting the alarm time to 0. If the user is inactive, the signal will not be canceled and after 5 seconds, the function timeout will be executed, firing the exception and consequently being captured by try, waxing the program.

The complete code would be:

import signal

def timeout(signum, frame):
    raise Exception('Seu tempo acabou!')

signal.signal(signal.SIGALRM, timeout)
signal.alarm(5)

try:
    signal.alarm(5)
    name = input('Qual é o seu nome? ')
    signal.alarm(0)
    print('Seja bem-vindo,', name)
except Exception as e:
    print(e)

See working on Repl.it | Github GIST

Obviously you can make everything much more beautiful by implementing a decorator in Python. For example:

import signal

def timeout(seconds):
    def decorator(function):
        def wrapper(*args, **kwargs):
            def handler(signum, frame):
                raise Exception(f'Timeout of {function.__name__} function')
            signal.signal(signal.SIGALRM, handler)
            signal.alarm(seconds)
            result = function(*args, **kwargs)
            signal.alarm(0)
            return result
        return wrapper
    return decorator

Thus, to define the timeout of any function, just do:

@timeout(seconds=5)
def read_user_name():
    name = input('Qual é o seu nome? ')
    print('Seja bem-vindo,', name)

And use it with:

try:
    read_user_name()
except Exception as e:
    print(e)

You can even use in other situations, such as downloading a file with the module requests:

@timeout(seconds=30)
def download_awesome_image(save):
    with open(save, 'wb') as stream:
        response = requests.get('http://url.to/awesome_image.jpg')
        stream.write(response.content)

try:
    download_awesome_image(save='awesome_image.jpg')
except Exception as e:
    print('Desculpe-me, mas demorou muito e eu não quis esperar')

Windows and others

An alternative that works also in Windows is to use the module multiprocessing, defining a distinct process to perform the task. Similarly to earlier, we can define a decorator:

from multiprocessing import TimeoutError
from multiprocessing.pool import ThreadPool

def timeout(seconds):
    def decorator(function):
        def wrapper(*args, **kwargs):
            pool = ThreadPool(processes=1)
            result = pool.apply_async(function, args=args, kwds=kwargs)
            try:
                return result.get(timeout=seconds)
            except TimeoutError as e:
                return e
        return wrapper
    return decorator

@timeout(5)
def read_user_name():
    return input('Nome? ')

name = read_user_name()

if isinstance(name, TimeoutError):
    print('Demorou demais, parsa!')
else:
    print('Olá', name)

See working on Repl.it | Github GIST

Note that as the function will be executed in another process, the exception that is triggered when time expires does not influence the original process, so to circumvent this, I returned the exception instance itself and did the type check before treating the variable value.

  • 1

    Dude, sensational, this designer turned into a Swiss army knife +1. Note: Signal is not fully compatible with all systems, at least not with Windows. Something that might work for Windows would be https://docs.python.org/3/library/threading.html#threading.Timer

1

I had this doubt a while ago. The way I found it was this.

import time

print('Pausa de 5 segundos (Aperte Ctrl-C para inserir algo)')
try:
    for i in range(0,5):
        time.sleep(1)    
except KeyboardInterrupt:
    input("Entrada: ")
  • 1

    Look, to be quite honest, I couldn’t decide whether I like this solution or not, but I left my vote for the fact that it is functional and for many applications it would be feasible to apply it for simplicity. Although it is still necessary to put that there is the question of the command Ctrl+C not stop running the program during this waiting time. You are altering the application’s natural behavior and this can generate side effects. You could add this to the answer?

  • The program for execution if it is in the console.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.