2010-02-17 44 views
116

Tôi có tập lệnh shell lặp qua một tệp văn bản chứa URL: mà tôi muốn truy cập và chụp ảnh màn hình.Chức năng hết thời gian chờ nếu mất quá nhiều thời gian để hoàn thành

Tất cả điều này được thực hiện và đơn giản. Kịch bản khởi tạo một lớp khi chạy sẽ tạo ra một ảnh chụp màn hình của mỗi trang trong danh sách. Một số trang web mất rất nhiều thời gian để tải và một số trang web có thể không tải được. Vì vậy, tôi muốn bọc hàm screengrabber trong một kịch bản hết giờ, làm cho hàm trả về False nếu nó không thể hoàn thành trong vòng 10 giây.

Tôi là nội dung có giải pháp đơn giản nhất có thể, có thể đặt bộ hẹn giờ không đồng bộ sẽ trả về Sai sau 10 giây cho dù điều gì thực sự xảy ra bên trong hàm?

+0

Đối với tất cả những người lười biếng, những người thích sử dụng thư viện thay vì sao chép + dán mã đoạn từ StackOverflow: https://pypi.python.org/pypi/timeout-decorator – guettli

Trả lời

186

Quá trình định thời gian cho một hoạt động được mô tả trong tài liệu cho signal.

Ý tưởng cơ bản là sử dụng trình xử lý tín hiệu để đặt báo thức trong một khoảng thời gian và tăng ngoại lệ sau khi bộ hẹn giờ hết hạn.

Lưu ý rằng điều này sẽ chỉ hoạt động trên UNIX.

Đây là triển khai tạo trang trí (lưu mã sau đây là 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 

Điều này tạo ra một trang trí được gọi là @timeout có thể áp dụng cho bất kỳ chức năng chạy dài nào.

Vì vậy, trong mã ứng dụng của bạn, bạn có thể sử dụng trang trí như vậy:

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

Hãy coi chừng rằng đây không phải là chủ đề an toàn: nếu bạn đang sử dụng đa luồng, tín hiệu sẽ bị bắt bởi một chuỗi ngẫu nhiên. Đối với các chương trình đơn luồng mặc dù, đây là giải pháp dễ nhất. – Wim

+1

Đẹp. Ngoài ra, bạn nên trang trí hàm 'wrapper' với' @ functools.wraps (func) ' – shx2

+6

FYI, có các dấu ngoặc bị thiếu sau" @timeout "đầu tiên. Nó nên đọc '@ timeout() def ...'. –

116

Tôi viết lại câu trả lời của David sử dụng câu lệnh with, nó cho phép bạn làm điều này:

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

nào sẽ tăng một TimeoutError.

Mã này được sử dụng vẫn signal và do đó UNIX chỉ:

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

+2

Bạn có thể dễ dàng thêm vào một trang trí '@ timeout.timeout' như một phương thức tĩnh cho điều này. Sau đó, bạn có thể dễ dàng lựa chọn giữa một trình trang trí hoặc câu lệnh 'with'. – Kevin

+5

Thú vị lưu ý rằng nếu bên trong bối cảnh 'với Timeout (t)' bất kỳ lỗi nào được nêu ra, '__exit__' vẫn được gọi, tránh, do đó, bất kỳ biến chứng nào gây ra bởi' TimeOutError' được nâng lên thay vì lỗi thực. Đây là một giải pháp rất đáng yêu. – lucastamoios

Các vấn đề liên quan