Batas waktu pada panggilan fungsi

300

Saya memanggil fungsi dengan Python yang saya tahu mungkin macet dan memaksa saya untuk me-restart script.

Bagaimana saya memanggil fungsi atau bagaimana saya membungkusnya sehingga jika dibutuhkan lebih dari 5 detik skrip membatalkannya dan melakukan sesuatu yang lain?

Teifion
sumber

Jawaban:

227

Anda dapat menggunakan paket sinyal jika Anda menggunakan UNIX:

In [1]: import signal

# Register an handler for the timeout
In [2]: def handler(signum, frame):
   ...:     print("Forever is over!")
   ...:     raise Exception("end of time")
   ...: 

# This function *may* run for an indetermined time...
In [3]: def loop_forever():
   ...:     import time
   ...:     while 1:
   ...:         print("sec")
   ...:         time.sleep(1)
   ...:         
   ...:         

# Register the signal function handler
In [4]: signal.signal(signal.SIGALRM, handler)
Out[4]: 0

# Define a timeout for your function
In [5]: signal.alarm(10)
Out[5]: 0

In [6]: try:
   ...:     loop_forever()
   ...: except Exception, exc: 
   ...:     print(exc)
   ....: 
sec
sec
sec
sec
sec
sec
sec
sec
Forever is over!
end of time

# Cancel the timer if the function returned before timeout
# (ok, mine won't but yours maybe will :)
In [7]: signal.alarm(0)
Out[7]: 0

10 detik setelah panggilan alarm.alarm(10), pawang dipanggil. Ini menimbulkan pengecualian bahwa Anda dapat mencegat dari kode Python biasa.

Modul ini tidak cocok dengan utas (tetapi kemudian, siapa yang melakukannya?)

Perhatikan bahwa karena kami memunculkan pengecualian ketika batas waktu terjadi, mungkin berakhir tertangkap dan diabaikan di dalam fungsi, misalnya salah satu fungsi tersebut:

def loop_forever():
    while 1:
        print('sec')
        try:
            time.sleep(10)
        except:
            continue
piro
sumber
5
Saya menggunakan Python 2.5.4. Ada kesalahan seperti itu: Traceback (panggilan terakhir terakhir): File "aa.py", line 85, dalam sinyal func.signal (signal.SIGALRM, handler) AttributeError: objek 'module' tidak memiliki atribut 'SIGALRM'
flypen
11
@flypen itu karena signal.alarmdan yang terkait SIGALRMtidak tersedia di platform Windows.
Dobel AA
2
Jika ada banyak proses, dan setiap panggilan signal.signal--- akankah mereka semua bekerja dengan baik? Tidak akankah setiap signal.signalpanggilan membatalkan panggilan "bersamaan"?
coklat
1
Peringatan bagi mereka yang ingin menggunakan ini dengan ekstensi C: Penangan sinyal Python tidak akan dipanggil sampai fungsi C mengembalikan kontrol ke juru bahasa Python. Untuk kasus penggunaan ini, gunakan jawaban ATOzTOA: stackoverflow.com/a/14924210/1286628
wkschwartz
13
Saya tegaskan peringatan tentang utas. signal.alarm hanya bekerja di utas utama. Saya mencoba menggunakan ini dalam pandangan Django - langsung gagal dengan bertele-tele tentang utas utama saja.
JL Peyret
154

Anda dapat menggunakannya multiprocessing.Processuntuk melakukan hal itu.

Kode

import multiprocessing
import time

# bar
def bar():
    for i in range(100):
        print "Tick"
        time.sleep(1)

if __name__ == '__main__':
    # Start bar as a process
    p = multiprocessing.Process(target=bar)
    p.start()

    # Wait for 10 seconds or until process finishes
    p.join(10)

    # If thread is still active
    if p.is_alive():
        print "running... let's kill it..."

        # Terminate
        p.terminate()
        p.join()
ATOzTOA
sumber
36
Bagaimana saya bisa mendapatkan nilai balik dari metode target?
bad_keypoints
4
Ini sepertinya tidak berfungsi jika fungsi yang dipanggil macet di blok I / O.
sudo
4
@bad_keypoints Lihat jawaban ini: stackoverflow.com/a/10415215/1384471 Pada dasarnya, Anda meneruskan daftar yang Anda masukkan jawabannya.
Peter
1
@sudo lalu hapus join(). yang membuat x jumlah Anda dari proses bersamaan berjalan hingga mereka menyelesaikan pekerjaannya, atau jumlah yang ditentukan dalam join(10). Jika Anda memiliki I / O pemblokiran untuk 10 proses, gunakan join (10) yang telah Anda tetapkan untuk menunggu semuanya max 10 untuk setiap proses yang sudah dimulai. Gunakan flag daemon seperti contoh ini stackoverflow.com/a/27420072/2480481 . Tentu saja Anda dapat melewati flag daemon=Truelangsung ke multiprocessing.Process()fungsi.
m3nda
2
@ATOzTOA masalah dengan solusi ini, setidaknya untuk tujuan saya, adalah bahwa hal itu berpotensi tidak memungkinkan anak-anak tapak untuk membersihkan sendiri. Dari dokumentasi fungsi terminateterminate() ... Note that exit handlers and finally clauses, etc., will not be executed. Note that descendant processes of the process will not be terminated – they will simply become orphaned.
abalcerek
78

Bagaimana saya memanggil fungsi atau bagaimana saya membungkusnya sehingga jika dibutuhkan lebih dari 5 detik skrip membatalkannya?

Saya memposting intisari yang memecahkan pertanyaan / masalah ini dengan dekorator dan a threading.Timer. Ini dia dengan gangguan.

Impor dan pengaturan untuk kompatibilitas

Itu diuji dengan Python 2 dan 3. Ini juga harus bekerja di bawah Unix / Linux dan Windows.

Pertama impor. Upaya ini untuk menjaga kode konsisten terlepas dari versi Python:

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

Gunakan kode independen versi:

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

Sekarang kami telah mengimpor fungsionalitas kami dari perpustakaan standar.

exit_after penghias

Selanjutnya kita membutuhkan fungsi untuk menghentikan main()dari thread anak:

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

Dan inilah dekorator itu sendiri:

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

Pemakaian

Dan inilah penggunaan yang langsung menjawab pertanyaan Anda tentang keluar setelah 5 detik !:

@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 "<stdin>", line 1, in <module>
  File "<stdin>", line 11, in inner
  File "<stdin>", line 6, in countdown
KeyboardInterrupt

Panggilan fungsi kedua tidak akan selesai, sebagai gantinya proses harus keluar dengan traceback!

KeyboardInterrupt tidak selalu menghentikan utas tidur

Perhatikan bahwa tidur tidak akan selalu terganggu oleh interupsi keyboard, pada Python 2 pada Windows, misalnya:

@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 "<stdin>", line 1, in <module>
  File "<stdin>", line 11, in inner
  File "<stdin>", line 3, in sleep10
KeyboardInterrupt

juga tidak akan mengganggu kode yang berjalan di ekstensi kecuali jika secara eksplisit memeriksa PyErr_CheckSignals(), lihat Cython, Python dan KeyboardInterrupt diabaikan

Saya akan menghindari tidur thread lebih dari satu detik, dalam hal apapun - itu satu tahun dalam waktu prosesor.

Bagaimana saya memanggil fungsi atau bagaimana saya membungkusnya sehingga jika dibutuhkan lebih dari 5 detik skrip membatalkannya dan melakukan sesuatu yang lain?

Untuk menangkapnya dan melakukan sesuatu yang lain, Anda dapat menangkap KeyboardInterrupt.

>>> try:
...     countdown(10)
... except KeyboardInterrupt:
...     print('do something else')
... 
countdown started
10, 9, 8, 7, 6, countdown took too long
do something else
Aaron Hall
sumber
Saya belum membaca seluruh posting Anda, tetapi saya hanya bertanya-tanya: bagaimana jika flush adalah 0? Itu akan ditafsirkan sebagai Salah dalam pernyataan if di bawahnya, kan?
Koenraad van Duin
2
Mengapa saya harus menelepon thread.interrupt_main(), mengapa saya tidak bisa langsung mengajukan pengecualian?
Anirban Nag 'tintinmj'
Adakah pemikiran untuk menyelesaikan multiprocessing.connection.Clientini? - Mencoba menyelesaikan: stackoverflow.com/questions/57817955/…
wwii
51

Saya punya proposal berbeda yang merupakan fungsi murni (dengan API yang sama dengan saran threading) dan tampaknya berfungsi dengan baik (berdasarkan saran di utas ini)

def timeout(func, args=(), kwargs={}, timeout_duration=1, default=None):
    import signal

    class TimeoutError(Exception):
        pass

    def handler(signum, frame):
        raise TimeoutError()

    # set the timeout handler
    signal.signal(signal.SIGALRM, handler) 
    signal.alarm(timeout_duration)
    try:
        result = func(*args, **kwargs)
    except TimeoutError as exc:
        result = default
    finally:
        signal.alarm(0)

    return result
Alex
sumber
3
Anda juga harus mengembalikan pengendali sinyal asli. Lihat stackoverflow.com/questions/492519/…
Martin Konecny
9
Satu lagi catatan: Metode sinyal Unix hanya berfungsi jika Anda menerapkannya di utas utama. Menerapkannya dalam sub-utas membuat pengecualian dan tidak akan berfungsi.
Martin Konecny
12
Ini bukan solusi terbaik karena hanya berfungsi di linux.
Maks
17
Max, not true - berfungsi pada sembarang unix yang sesuai dengan POSIX. Saya pikir komentar Anda harus lebih akurat, tidak berfungsi di Windows.
Chris Johnson
6
Anda harus menghindari pengaturan kwargs ke dict kosong. Gotcha Python yang umum adalah bahwa argumen default pada fungsi bisa berubah. Sehingga kamus itu akan dibagikan di semua panggilan ke timeout. Jauh lebih baik untuk mengatur default ke Nonedan, pada baris pertama fungsi, tambahkan kwargs = kwargs or {}. Args tidak apa-apa karena tupel tidak bisa berubah.
scottmrogowski
32

Saya berlari melintasi utas ini ketika mencari panggilan timeout pada unit test. Saya tidak menemukan sesuatu yang sederhana dalam jawaban atau paket pihak ke-3 jadi saya menulis dekorator di bawah ini yang dapat Anda masukkan ke dalam kode:

import multiprocessing.pool
import functools

def timeout(max_timeout):
    """Timeout decorator, parameter in seconds."""
    def timeout_decorator(item):
        """Wrap the original function."""
        @functools.wraps(item)
        def func_wrapper(*args, **kwargs):
            """Closure for function."""
            pool = multiprocessing.pool.ThreadPool(processes=1)
            async_result = pool.apply_async(item, args, kwargs)
            # raises a TimeoutError if execution exceeds max_timeout
            return async_result.get(max_timeout)
        return func_wrapper
    return timeout_decorator

Maka sesederhana ini untuk timeout tes atau fungsi apa pun yang Anda suka:

@timeout(5.0)  # if execution takes longer than 5 seconds, raise a TimeoutError
def test_base_regression(self):
    ...
Kaya
sumber
14
Hati-hati karena ini tidak menghentikan fungsi setelah batas waktu tercapai!
Sylvain
Perhatikan bahwa pada Windows, ini memunculkan proses yang sama sekali baru - yang akan memakan waktu hingga habis, mungkin dengan banyak jika ketergantungan memerlukan waktu lama untuk mengatur.
Aaron Hall
1
Ya, ini perlu beberapa penyesuaian. Ia meninggalkan utas selamanya.
sudo
2
IDK jika ini adalah cara terbaik, tetapi Anda dapat mencoba / menangkap Exceptionbagian dalam func_wrapper dan lakukan pool.close()setelah tangkapan untuk memastikan utas selalu mati sesudahnya bagaimanapun caranya . Kemudian Anda bisa melempar TimeoutErroratau apa pun yang Anda inginkan. Tampaknya bekerja untuk saya.
sudo
2
Ini berguna, tetapi begitu saya sering melakukannya, saya mengerti RuntimeError: can't start new thread. Akankah ini masih berfungsi jika saya mengabaikannya atau ada hal lain yang dapat saya lakukan untuk menyiasati hal ini? Terima kasih sebelumnya!
Benjie
20

The stopitpaket, ditemukan di pypi, tampaknya untuk menangani timeout dengan baik.

Saya suka @stopit.threading_timeoutabledekorator, yang menambahkan timeoutparameter ke fungsi yang didekorasi, yang melakukan apa yang Anda harapkan, itu menghentikan fungsi.

Lihat di pypi: https://pypi.python.org/pypi/stopit

egeland
sumber
1
Ini sangat praktis dan aman untuk digunakan! Terima kasih dan Plus satu! Ini adalah pilihan terbaik yang saya temukan sejauh ini dan bahkan lebih baik daripada jawaban yang diterima !!
Yahya
Library mengklaim, beberapa fungsi tidak berfungsi di Windows.
Stefan Simik
16

Ada banyak saran, tetapi tidak ada yang menggunakan concurrent.futures, yang saya pikir adalah cara yang paling mudah dibaca untuk menangani ini.

from concurrent.futures import ProcessPoolExecutor

# Warning: this does not terminate function if timeout
def timeout_five(fnc, *args, **kwargs):
    with ProcessPoolExecutor() as p:
        f = p.submit(fnc, *args, **kwargs)
        return f.result(timeout=5)

Sangat mudah dibaca dan dikelola.

Kami membuat kumpulan, mengirimkan satu proses dan kemudian menunggu hingga 5 detik sebelum menaikkan TimeoutError yang dapat Anda tangkap dan tangani sesuai kebutuhan.

Berasal dari python 3.2+ dan backported ke 2.7 (pip install futures).

Beralih antara utas dan proses semudah mengganti ProcessPoolExecutordengan ThreadPoolExecutor.

Jika Anda ingin menghentikan Proses pada batas waktu saya sarankan melihat ke Pebble .

Brian
sumber
2
Apa maksudnya "Peringatan: ini tidak menghentikan fungsi jika batas waktu"?
Scott Stafford
5
@ScottStafford Proses / utas tidak berakhir hanya karena TimeoutError telah dimunculkan. Jadi proses atau utas masih akan mencoba untuk menjalankan sampai selesai dan tidak akan secara otomatis memberikan Anda kontrol kembali pada batas waktu Anda.
Brian
Apakah ini akan membiarkan saya menyimpan hasil yang sedang pada saat itu? misalnya jika saya memiliki fungsi rekursif yang saya atur batas waktunya menjadi 5, dan pada waktu itu saya memiliki hasil parsial, bagaimana cara saya menulis fungsi untuk mengembalikan hasil parsial pada batas waktu?
SumNeuron
Saya menggunakan ini, namun saya memiliki 1000 tugas, masing-masing diperbolehkan 5 detik sebelum batas waktu. Masalah saya adalah bahwa core tersumbat pada tugas yang tidak pernah berakhir karena batas waktu hanya diterapkan pada total tugas bukan pada tugas individu. concurrent.futures tidak memberikan solusi untuk afaik ini.
Bastiaan
12

Hebat, dekorator dekor proyek PyPi , mudah digunakan, dan dapat diandalkan ( https://pypi.org/project/timeout-decorator/ )

instalasi :

pip install timeout-decorator

Penggunaan :

import time
import timeout_decorator

@timeout_decorator.timeout(5)
def mytest():
    print "Start"
    for i in range(1,10):
        time.sleep(1)
        print "%d seconds have passed" % i

if __name__ == '__main__':
    mytest()
Gil
sumber
2
Saya menghargai solusi yang jelas. Tapi siapa pun bisa menjelaskan bagaimana perpustakaan ini bekerja, terutama ketika berhadapan dengan multithreading. Secara pribadi saya takut menggunakan machanism yang tidak dikenal untuk menangani utas atau sinyal.
wsysuper
@wsysuper lib memiliki 2 mode operasi: buka utas baru atau subproses baru (yang seharusnya aman utas)
Gil
ini bekerja sangat baik untuk saya!
Florian Heigl
6

Saya penulis wrapt_timeout_decorator

Sebagian besar solusi yang disajikan di sini bekerja secara diam-diam di Linux pada pandangan pertama - karena kita memiliki fork () dan sinyal () - tetapi pada windows semuanya terlihat sedikit berbeda. Dan ketika datang ke subthreads di Linux, Anda tidak dapat menggunakan Sinyal lagi.

Untuk memunculkan proses di bawah Windows, itu harus dipilih - dan banyak fungsi yang didekorasi atau metode Kelas tidak.

Jadi, Anda perlu menggunakan pickler yang lebih baik seperti dill dan multiprocess (bukan acar dan multiprocessing) - itu sebabnya Anda tidak dapat menggunakan ProcessPoolExecutor (atau hanya dengan fungsi terbatas).

Untuk batas waktu itu sendiri - Anda perlu menentukan apa arti batas waktu - karena pada Windows perlu waktu yang lama (dan tidak dapat ditentukan) untuk menelurkan proses. Ini bisa rumit pada batas waktu singkat. Mari kita asumsikan, proses pemijahan membutuhkan waktu sekitar 0,5 detik (mudah !!!). Jika Anda memberi batas waktu 0,2 detik, apa yang harus terjadi? Haruskah fungsi waktu habis setelah 0,5 + 0,2 detik (jadi biarkan metode berjalan selama 0,2 detik)? Atau haruskah proses yang disebut time out setelah 0,2 detik (dalam kasus itu, fungsi yang didekorasi akan SELALU habis, karena pada waktu itu ia bahkan tidak muncul)?

Dekorator bersarang juga bisa jahat dan Anda tidak dapat menggunakan Sinyal di subthread. Jika Anda ingin membuat dekorator lintas platform yang benar-benar universal, semua ini perlu dipertimbangkan (dan diuji).

Masalah lain adalah memberikan pengecualian kembali ke pemanggil, serta masalah pencatatan (jika digunakan dalam fungsi yang didekorasi - masuk ke file dalam proses lain TIDAK didukung)

Saya mencoba untuk menutupi semua kasus tepi, Anda mungkin melihat ke dalam paket wrapt_timeout_decorator, atau setidaknya menguji solusi Anda sendiri yang terinspirasi oleh unittests yang digunakan di sana.

@Alexis Eggermont - sayangnya saya tidak punya cukup poin untuk berkomentar - mungkin orang lain dapat memberi tahu Anda - saya pikir saya telah memecahkan masalah impor Anda.

bitranox
sumber
3

timeout-decoratortidak berfungsi pada sistem windows karena, windows tidak mendukung dengan signalbaik.

Jika Anda menggunakan timeout-decorator di sistem windows Anda akan mendapatkan yang berikut ini

AttributeError: module 'signal' has no attribute 'SIGALRM'

Beberapa menyarankan untuk menggunakan use_signals=Falsetetapi tidak berhasil untuk saya.

Penulis @bitranox membuat paket berikut:

pip install https://github.com/bitranox/wrapt-timeout-decorator/archive/master.zip

Contoh kode:

import time
from wrapt_timeout_decorator import *

@timeout(5)
def mytest(message):
    print(message)
    for i in range(1,10):
        time.sleep(1)
        print('{} seconds have passed'.format(i))

def main():
    mytest('starting')


if __name__ == '__main__':
    main()

Memberikan pengecualian berikut:

TimeoutError: Function mytest timed out after 5 seconds
seolah-olah
sumber
Ini terdengar seperti solusi yang sangat bagus. Anehnya, antrean from wrapt_timeout_decorator import * tampaknya membunuh beberapa impor saya yang lain. Misalnya saya dapat ModuleNotFoundError: No module named 'google.appengine', tetapi saya tidak mendapatkan kesalahan ini jika saya tidak mengimpor wrapt_timeout_decorator
Alexis Eggermont
@AlexisEggermont Saya akan menggunakan ini dengan appengine ... jadi saya sangat ingin tahu apakah kesalahan ini berlanjut?
PascalVKooten
2

Kita dapat menggunakan sinyal yang sama. Saya pikir contoh di bawah ini akan berguna untuk Anda. Ini sangat sederhana dibandingkan dengan utas.

import signal

def timeout(signum, frame):
    raise myException

#this is an infinite loop, never ending under normal circumstances
def main():
    print 'Starting Main ',
    while 1:
        print 'in main ',

#SIGALRM is only usable on a unix platform
signal.signal(signal.SIGALRM, timeout)

#change 5 to however many seconds you need
signal.alarm(5)

try:
    main()
except myException:
    print "whoops"
AR
sumber
1
Akan lebih baik untuk memilih pengecualian tertentu dan hanya menangkapnya. Telanjang try: ... except: ...selalu merupakan ide yang buruk.
hivert
Saya setuju dengan Anda, hivert.
AR
sementara saya mengerti alasannya, sebagai sysadmin / integrator saya tidak setuju - kode python terkenal karena mengabaikan penanganan kesalahan, dan menangani satu hal yang Anda harapkan tidak cukup baik untuk perangkat lunak berkualitas. Anda dapat menangani 5 hal yang Anda rencanakan DAN strategi generik untuk hal-hal lain. "Traceback, None" bukan strategi, ini penghinaan.
Florian Heigl
2
#!/usr/bin/python2
import sys, subprocess, threading
proc = subprocess.Popen(sys.argv[2:])
timer = threading.Timer(float(sys.argv[1]), proc.terminate)
timer.start()
proc.wait()
timer.cancel()
exit(proc.returncode)
Hal Canary
sumber
7
Sementara kode ini dapat menjawab pertanyaan, memberikan konteks tambahan tentang bagaimana dan / atau mengapa memecahkan masalah akan meningkatkan nilai jangka panjang jawaban
Dan Cornilescu
1

Saya memiliki kebutuhan untuk interupsi berjangka waktu nestable (yang SIGALARM tidak dapat melakukan) yang tidak akan diblokir oleh time.sleep (yang pendekatan berbasis thread tidak dapat melakukan). Saya akhirnya menyalin dan memodifikasi sedikit kode dari sini: http://code.activestate.com/recipes/577600-queue-for-managing-multiple-sigalrm-alarms-concurr/

Kode itu sendiri:

#!/usr/bin/python

# lightly modified version of http://code.activestate.com/recipes/577600-queue-for-managing-multiple-sigalrm-alarms-concurr/


"""alarm.py: Permits multiple SIGALRM events to be queued.

Uses a `heapq` to store the objects to be called when an alarm signal is
raised, so that the next alarm is always at the top of the heap.
"""

import heapq
import signal
from time import time

__version__ = '$Revision: 2539 $'.split()[1]

alarmlist = []

__new_alarm = lambda t, f, a, k: (t + time(), f, a, k)
__next_alarm = lambda: int(round(alarmlist[0][0] - time())) if alarmlist else None
__set_alarm = lambda: signal.alarm(max(__next_alarm(), 1))


class TimeoutError(Exception):
    def __init__(self, message, id_=None):
        self.message = message
        self.id_ = id_


class Timeout:
    ''' id_ allows for nested timeouts. '''
    def __init__(self, id_=None, seconds=1, error_message='Timeout'):
        self.seconds = seconds
        self.error_message = error_message
        self.id_ = id_
    def handle_timeout(self):
        raise TimeoutError(self.error_message, self.id_)
    def __enter__(self):
        self.this_alarm = alarm(self.seconds, self.handle_timeout)
    def __exit__(self, type, value, traceback):
        try:
            cancel(self.this_alarm) 
        except ValueError:
            pass


def __clear_alarm():
    """Clear an existing alarm.

    If the alarm signal was set to a callable other than our own, queue the
    previous alarm settings.
    """
    oldsec = signal.alarm(0)
    oldfunc = signal.signal(signal.SIGALRM, __alarm_handler)
    if oldsec > 0 and oldfunc != __alarm_handler:
        heapq.heappush(alarmlist, (__new_alarm(oldsec, oldfunc, [], {})))


def __alarm_handler(*zargs):
    """Handle an alarm by calling any due heap entries and resetting the alarm.

    Note that multiple heap entries might get called, especially if calling an
    entry takes a lot of time.
    """
    try:
        nextt = __next_alarm()
        while nextt is not None and nextt <= 0:
            (tm, func, args, keys) = heapq.heappop(alarmlist)
            func(*args, **keys)
            nextt = __next_alarm()
    finally:
        if alarmlist: __set_alarm()


def alarm(sec, func, *args, **keys):
    """Set an alarm.

    When the alarm is raised in `sec` seconds, the handler will call `func`,
    passing `args` and `keys`. Return the heap entry (which is just a big
    tuple), so that it can be cancelled by calling `cancel()`.
    """
    __clear_alarm()
    try:
        newalarm = __new_alarm(sec, func, args, keys)
        heapq.heappush(alarmlist, newalarm)
        return newalarm
    finally:
        __set_alarm()


def cancel(alarm):
    """Cancel an alarm by passing the heap entry returned by `alarm()`.

    It is an error to try to cancel an alarm which has already occurred.
    """
    __clear_alarm()
    try:
        alarmlist.remove(alarm)
        heapq.heapify(alarmlist)
    finally:
        if alarmlist: __set_alarm()

dan contoh penggunaan:

import alarm
from time import sleep

try:
    with alarm.Timeout(id_='a', seconds=5):
        try:
            with alarm.Timeout(id_='b', seconds=2):
                sleep(3)
        except alarm.TimeoutError as e:
            print 'raised', e.id_
        sleep(30)
except alarm.TimeoutError as e:
    print 'raised', e.id_
else:
    print 'nope.'
James
sumber
Ini juga menggunakan sinyal maka tidak akan berfungsi jika dipanggil dari utas.
garg10mungkin
0

Berikut ini sedikit peningkatan pada solusi berbasis thread yang diberikan.

Kode di bawah ini mendukung pengecualian :

def runFunctionCatchExceptions(func, *args, **kwargs):
    try:
        result = func(*args, **kwargs)
    except Exception, message:
        return ["exception", message]

    return ["RESULT", result]


def runFunctionWithTimeout(func, args=(), kwargs={}, timeout_duration=10, default=None):
    import threading
    class InterruptableThread(threading.Thread):
        def __init__(self):
            threading.Thread.__init__(self)
            self.result = default
        def run(self):
            self.result = runFunctionCatchExceptions(func, *args, **kwargs)
    it = InterruptableThread()
    it.start()
    it.join(timeout_duration)
    if it.isAlive():
        return default

    if it.result[0] == "exception":
        raise it.result[1]

    return it.result[1]

Menjalankannya dengan batas waktu 5 detik:

result = timeout(remote_calculate, (myarg,), timeout_duration=5)
diemacht
sumber
1
Ini akan memunculkan pengecualian baru menyembunyikan traceback asli. Lihat versi saya di bawah ini ...
Meitham
1
Ini juga tidak aman, seolah-olah dalam runFunctionCatchExceptions()fungsi Python tertentu memperoleh GIL disebut. Misalnya berikut tidak akan pernah, atau untuk waktu yang sangat lama, kembali jika disebut dalam fungsi: eval(2**9999999999**9999999999). Lihat stackoverflow.com/questions/22138190/...
Mikko Ohtamaa