Timeout on a function call

后端 未结 18 1488
挽巷
挽巷 2020-11-21 04:53

I\'m calling a function in Python which I know may stall and force me to restart the script.

How do I call the function or what do I wrap it in so that if it takes

18条回答
  •  盖世英雄少女心
    2020-11-21 04:59

    How do I call the function or what do I wrap it in so that if it takes longer than 5 seconds the script cancels it?

    I posted a gist that solves this question/problem with a decorator and a threading.Timer. Here it is with a breakdown.

    Imports and setups for compatibility

    It was tested with Python 2 and 3. It should also work under Unix/Linux and Windows.

    First the imports. These attempt to keep the code consistent regardless of the Python version:

    from __future__ import print_function
    import sys
    import threading
    from time import sleep
    try:
        import thread
    except ImportError:
        import _thread as thread
    

    Use version independent code:

    try:
        range, _print = xrange, print
        def print(*args, **kwargs): 
            flush = kwargs.pop('flush', False)
            _print(*args, **kwargs)
            if flush:
                kwargs.get('file', sys.stdout).flush()            
    except NameError:
        pass
    

    Now we have imported our functionality from the standard library.

    exit_after decorator

    Next we need a function to terminate the main() from the child thread:

    def quit_function(fn_name):
        # print to stderr, unbuffered in Python 2.
        print('{0} took too long'.format(fn_name), file=sys.stderr)
        sys.stderr.flush() # Python 3 stderr is likely buffered.
        thread.interrupt_main() # raises KeyboardInterrupt
    

    And here is the decorator itself:

    def exit_after(s):
        '''
        use as decorator to exit process if 
        function takes longer than s seconds
        '''
        def outer(fn):
            def inner(*args, **kwargs):
                timer = threading.Timer(s, quit_function, args=[fn.__name__])
                timer.start()
                try:
                    result = fn(*args, **kwargs)
                finally:
                    timer.cancel()
                return result
            return inner
        return outer
    

    Usage

    And here's the usage that directly answers your question about exiting after 5 seconds!:

    @exit_after(5)
    def countdown(n):
        print('countdown started', flush=True)
        for i in range(n, -1, -1):
            print(i, end=', ', flush=True)
            sleep(1)
        print('countdown finished')
    

    Demo:

    >>> countdown(3)
    countdown started
    3, 2, 1, 0, countdown finished
    >>> countdown(10)
    countdown started
    10, 9, 8, 7, 6, countdown took too long
    Traceback (most recent call last):
      File "", line 1, in 
      File "", line 11, in inner
      File "", line 6, in countdown
    KeyboardInterrupt
    

    The second function call will not finish, instead the process should exit with a traceback!

    KeyboardInterrupt does not always stop a sleeping thread

    Note that sleep will not always be interrupted by a keyboard interrupt, on Python 2 on Windows, e.g.:

    @exit_after(1)
    def sleep10():
        sleep(10)
        print('slept 10 seconds')
    
    >>> sleep10()
    sleep10 took too long         # Note that it hangs here about 9 more seconds
    Traceback (most recent call last):
      File "", line 1, in 
      File "", line 11, in inner
      File "", line 3, in sleep10
    KeyboardInterrupt
    

    nor is it likely to interrupt code running in extensions unless it explicitly checks for PyErr_CheckSignals(), see Cython, Python and KeyboardInterrupt ignored

    I would avoid sleeping a thread more than a second, in any case - that's an eon in processor time.

    How do I call the function or what do I wrap it in so that if it takes longer than 5 seconds the script cancels it and does something else?

    To catch it and do something else, you can catch the KeyboardInterrupt.

    >>> try:
    ...     countdown(10)
    ... except KeyboardInterrupt:
    ...     print('do something else')
    ... 
    countdown started
    10, 9, 8, 7, 6, countdown took too long
    do something else
    

提交回复
热议问题