2010-02-17 103 views
116

我有一個shell腳本,通過包含URL的文本文件進行循環,該文件包含我想要訪問並截取的URL。超時功能,如果它需要太長時間才能完成

所有這一切都很簡單。該腳本初始化一個類,該類在運行時創建列表中每個站點的屏幕截圖。有些網站需要很長時間才能加載,有些網站可能根本無法加載。所以我想在一個超時腳本中包裝screengrabber函數,如果函數在10秒內無法完成,則返回False

我滿足於最簡單的解決方案,也許設置一個異步計時器,在10秒後返回False,而不管函數內部發生了什麼?

+0

對於所有懶惰的人,誰愛用的庫而不是複製粘貼+代碼片段來自StackOverflow的:https://pypi.python.org/pypi/timeout-decorator – guettli 2017-05-02 12:42:41

回答

186

signal的文檔中描述了超時操作的過程。

其基本思想是使用信號處理程序在某個時間間隔內設置警報,並在該定時器到期後引發異常。

請注意,這隻適用於UNIX。

以下是創建裝飾器的實現(將以下代碼保存爲timeout.py)。

from functools import wraps 
import errno 
import os 
import signal 

class TimeoutError(Exception): 
    pass 

def timeout(seconds=10, error_message=os.strerror(errno.ETIME)): 
    def decorator(func): 
     def _handle_timeout(signum, frame): 
      raise TimeoutError(error_message) 

     def wrapper(*args, **kwargs): 
      signal.signal(signal.SIGALRM, _handle_timeout) 
      signal.alarm(seconds) 
      try: 
       result = func(*args, **kwargs) 
      finally: 
       signal.alarm(0) 
      return result 

     return wraps(func)(wrapper) 

    return decorator 

這創建了一個名爲@timeout的裝飾器,可以應用於任何長時間運行的函數。

因此,在應用程序代碼,您可以使用像這樣的裝飾:

from timeout import timeout 

# Timeout a long running function with the default expiry of 10 seconds. 
@timeout 
def long_running_function1(): 
    ... 

# Timeout after 5 seconds 
@timeout(5) 
def long_running_function2(): 
    ... 

# Timeout after 30 seconds, with the error "Connection timed out" 
@timeout(30, os.strerror(errno.ETIMEDOUT)) 
def long_running_function3(): 
    ... 
+50

請注意,這不是線程安全的:如果您使用多線程,信號將被隨機線程捕獲。對於單線程程序,這是最簡單的解決方案。 – Wim 2010-02-17 17:03:11

+1

不錯。另外,建議使用@ functools.wrap(func)' – shx2 2013-10-31 19:58:05

+6

FYI修飾函數'wrapper',在第一個「@timeout」之後會丟失parens。它應該讀取'@timeout()def ...'。 – 2014-03-16 22:28:04

116

我重寫使用with聲明大衛的回答,它可以讓你做這樣做:

with timeout(seconds=3): 
    time.sleep(4) 

哪會引發TimeoutError。

的代碼仍然使用signal,因此僅適用於UNIX:

import signal 

class timeout: 
    def __init__(self, seconds=1, error_message='Timeout'): 
     self.seconds = seconds 
     self.error_message = error_message 
    def handle_timeout(self, signum, frame): 
     raise TimeoutError(self.error_message) 
    def __enter__(self): 
     signal.signal(signal.SIGALRM, self.handle_timeout) 
     signal.alarm(self.seconds) 
    def __exit__(self, type, value, traceback): 
     signal.alarm(0) 
+6

Python Framester 2014-10-02 09:17:48

+2

你可以很容易地添加一個裝飾器'@ timeout.timeout'作爲靜態方法。然後,您可以輕鬆地在裝飾器或「with」語句之間進行選擇。 – Kevin 2015-10-15 18:09:58

+5

有趣的是,如果在'Timeout(t)'上下文中引發任何錯誤,'__exit__'仍然會被調用,從而避免由引發'TimeOutError'而不是真正的錯誤引起的任何複雜性。這是一個非常可愛的解決方案。 – lucastamoios 2016-10-30 13:38:03

相關問題