简体   繁体   English

如何在 Python 的 for 循环中创建超时?

[英]How do you create a timeout in a for-loop in Python?

I have a for loop that retrieves data from an API:我有一个从 API 检索数据的 for 循环:

app = WebService()
for i in items:
    result = app.request(item)

I want to create a timeout so that, if the app.request blocking call takes too long, the loop will skip it and go to the next item.我想创建一个超时,这样,如果app.request阻塞调用花费太长时间,循环将跳过它,go 到下一个项目。

I have read some ways of creating a timer by using a while loop, but I believe in my case I cannot create a while loop inside the for loop with a continue clause that would apply to the for loop... So, how can I do this?我已经阅读了一些使用while循环创建计时器的方法,但我相信在我的情况下,我无法在 for 循环中创建一个while循环,并使用适用for forcontinue子句......那么,我该怎么做做这个?

Unfortunately, the API doesn't provide a way to create a timeout.不幸的是,API 没有提供创建超时的方法。 It is not an HTTP call to a REST API.它不是对 REST API 的 HTTP 调用。

Based on this answer .基于这个答案

This is a good use-case for a decorator .这是一个很好的装饰器用例。 The decorator pattern is useful when you want to wrap a function or class method with additional functionality.当您想要包装具有附加功能的 function 或 class 方法时,装饰器模式很有用。

This is the equivalent of how to do a Python 3 timeout decorator using the Python's signal library.这相当于如何使用 Python 的signal库执行 Python 3 超时装饰器。

Once you have the decorator, wrap your app.request in a decorated function and handle the exception with a try-except.一旦你有了装饰器,将你的 app.request 包装在一个decorated过的 function 中,并使用 try-except 处理异常。


# main.py
import signal

DEBUG = True

# Custom exception. In general, it's a good practice.
class TimeoutError(Exception):
    def __init__(self, value = "Timed Out"):
        self.value = value
    def __str__(self):
        return repr(self.value)

# This is the decorator itself.
# Read about it here: https://pythonbasics.org/decorators/
# Basically, it's a function that receives another function and a time parameter, i.e. the number of seconds.
# Then, it wraps it so that it raises an error if the function does not
# return a result before `seconds_before_timeout` seconds
def timeout(seconds_before_timeout):
  def decorate(f):
    if DEBUG: print("[DEBUG]: Defining decorator handler and the new function")
    if DEBUG: print(f"[DEBUG]: Received the following function >> `{f.__name__}`")
    def handler(signum, frame):
      raise TimeoutError()
    def new_f(*args, **kwargs):
      # Verbatim from Python Docs
      # > The signal.signal() function allows defining custom handlers to be executed
      #   when a signal is received.
      if DEBUG: print(f"[DEBUG]: in case of ALARM for function `{f.__name__}`, I'll handle it with the... `handler`")
      old = signal.signal(signal.SIGALRM, handler)

      # See https://docs.python.org/3/library/signal.html#signal.alarm
      if DEBUG: print(f"[DEBUG]: setting an alarm for {seconds_before_timeout} seconds.")
      signal.alarm(seconds_before_timeout)
      try:
          if DEBUG: print(f"[DEBUG]: executing `{f.__name__}`...")
          result = f(*args, **kwargs)
      finally:
          # reinstall the old signal handler
          signal.signal(signal.SIGALRM, old)
          # Cancel alarm. 
          # See: https://docs.python.org/3/library/signal.html#signal.alarm
          signal.alarm(0)
      return result
    
    new_f.__name__ = f.__name__
    return new_f
  return decorate

import time

@timeout(5)
def mytest():
    for i in range(1,10):
      interval = 2
      if DEBUG: print("[DEBUG]: waiting 2 seconds... on purpose")
      time.sleep(interval)
      print("%d seconds have passed" % (interval * i))

if __name__ == '__main__':
  if DEBUG: print("[DEBUG]: Starting program")
  mytest()

You can try it fast on this repl.it您可以在此 repl.it上快速尝试


Additionally, if you do not want to "hide" the API call inside the function, you can do dependency inversion.此外,如果您不想在 function 中“隐藏” API 调用,则可以进行依赖倒置。 That is, your decorated function does not depend on any request function implementation in particular.也就是说,您装饰的 function 不依赖于任何request function 实现。 To do this, you receive the function itself as an argument.为此,您收到 function 本身作为参数。 See below:见下文:

# simple decoration
@timeout(5)
def make_request(item):
    # assuming `app` is defined
    return app.request(item)

# dependency inversion

@timeout(5)
def make_request(request_handler, item):
    return request_handler(item)

# and then in your loop...
for i in items:
    make_request(app.request, item)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM