복붙노트

[PYTHON] 파이썬에서 함수에 타임 아웃을 추가하는 법

PYTHON

파이썬에서 함수에 타임 아웃을 추가하는 법

파이썬에서 시간 제한 기능을 추가하기 위해 과거에 많은 시도가있었습니다. 예를 들어, 지정된 시간 제한이 만료되면 대기 코드가 계속 움직일 수 있습니다. 불행하게도, 이전의 방법은 실행중인 함수가 계속 실행되고 리소스를 소비하거나 플랫폼에 특정한 스레드 종료 방법을 사용하여 함수를 종료 할 수있었습니다. 이 위키의 목적은 많은 프로그래머가 다양한 프로그래밍 프로젝트를 수행해야했던이 문제에 대한 크로스 플랫폼 응답을 개발하는 것입니다.

#! /usr/bin/env python
"""Provide way to add timeout specifications to arbitrary functions.

There are many ways to add a timeout to a function, but no solution
is both cross-platform and capable of terminating the procedure. This
module use the multiprocessing module to solve both of those problems."""

################################################################################

__author__ = 'Stephen "Zero" Chappell <Noctis.Skytower@gmail.com>'
__date__ = '11 February 2010'
__version__ = '$Revision: 3 $'

################################################################################

import inspect
import sys
import time
import multiprocessing

################################################################################

def add_timeout(function, limit=60):
    """Add a timeout parameter to a function and return it.

    It is illegal to pass anything other than a function as the first
    parameter. If the limit is not given, it gets a default value equal
    to one minute. The function is wrapped and returned to the caller."""
    assert inspect.isfunction(function)
    if limit <= 0:
        raise ValueError()
    return _Timeout(function, limit)

class NotReadyError(Exception): pass

################################################################################

def _target(queue, function, *args, **kwargs):
    """Run a function with arguments and return output via a queue.

    This is a helper function for the Process created in _Timeout. It runs
    the function with positional arguments and keyword arguments and then
    returns the function's output by way of a queue. If an exception gets
    raised, it is returned to _Timeout to be raised by the value property."""
    try:
        queue.put((True, function(*args, **kwargs)))
    except:
        queue.put((False, sys.exc_info()[1]))

class _Timeout:

    """Wrap a function and add a timeout (limit) attribute to it.

    Instances of this class are automatically generated by the add_timeout
    function defined above. Wrapping a function allows asynchronous calls
    to be made and termination of execution after a timeout has passed."""

    def __init__(self, function, limit):
        """Initialize instance in preparation for being called."""
        self.__limit = limit
        self.__function = function
        self.__timeout = time.clock()
        self.__process = multiprocessing.Process()
        self.__queue = multiprocessing.Queue()

    def __call__(self, *args, **kwargs):
        """Execute the embedded function object asynchronously.

        The function given to the constructor is transparently called and
        requires that "ready" be intermittently polled. If and when it is
        True, the "value" property may then be checked for returned data."""
        self.cancel()
        self.__queue = multiprocessing.Queue(1)
        args = (self.__queue, self.__function) + args
        self.__process = multiprocessing.Process(target=_target,
                                                 args=args,
                                                 kwargs=kwargs)
        self.__process.daemon = True
        self.__process.start()
        self.__timeout = self.__limit + time.clock()

    def cancel(self):
        """Terminate any possible execution of the embedded function."""
        if self.__process.is_alive():
            self.__process.terminate()

    @property
    def ready(self):
        """Read-only property indicating status of "value" property."""
        if self.__queue.full():
            return True
        elif not self.__queue.empty():
            return True
        elif self.__timeout < time.clock():
            self.cancel()
        else:
            return False

    @property
    def value(self):
        """Read-only property containing data returned from function."""
        if self.ready is True:
            flag, load = self.__queue.get()
            if flag:
                return load
            raise load
        raise NotReadyError()

    def __get_limit(self):
        return self.__limit

    def __set_limit(self, value):
        if value <= 0:
            raise ValueError()
        self.__limit = value

    limit = property(__get_limit, __set_limit,
                     doc="Property for controlling the value of the timeout.")

편집 :이 코드는 Python 3.x 용으로 작성되었으며 클래스 메소드 용으로 설계되지 않았습니다. 다중 처리 모듈은 프로세스 경계에서 클래스 인스턴스를 수정하도록 설계되지 않았습니다.

해결법

  1. ==============================

    1.여러분의 코드에서 가장 중요한 문제는 서브 클래스 화되지 않는 클래스에서 두 개의 밑줄 네임 스페이스 충돌 방지를 과도하게 사용한다는 것입니다.

    여러분의 코드에서 가장 중요한 문제는 서브 클래스 화되지 않는 클래스에서 두 개의 밑줄 네임 스페이스 충돌 방지를 과도하게 사용한다는 것입니다.

    일반적으로, self .__ foo는 코드 라인의 주석을 따라야하는 코드 냄새입니다. 이것은 믹스인데 임의의 서브 클래스가 네임 스페이스 충돌을 일으키는 것을 원하지 않습니다.

    이 메소드의 클라이언트 API는 다음과 같습니다.

    def mymethod(): pass
    
    mymethod = add_timeout(mymethod, 15)
    
    # start the processing    
    timeout_obj = mymethod()
    try:
        # access the property, which is really a function call
        ret = timeout_obj.value
    except TimeoutError:
        # handle a timeout here
        ret = None
    

    이것은 매우 비단뱀이 아니며 더 나은 클라이언트 API는 다음과 같습니다.

    @timeout(15)
    def mymethod(): pass
    
    try:
        my_method()
    except TimeoutError:
        pass
    

    클래스에서 @property를 사용하여 상태를 변경하는 접근자인 무언가를 사용하고 있습니다. 이는 좋은 생각이 아닙니다. 예를 들어 .value가 두 번 액세스 될 때 어떤 일이 발생합니까? 대기열이 이미 비어 있기 때문에 queue.get ()이 휴지통을 반환하므로 실패 할 것으로 보입니다.

    @property를 완전히 제거하십시오. 이런 맥락에서 사용하지 마십시오. 사용 사례에 적합하지 않습니다. 호출 될 때 호출 블록을 만들고 값을 반환하거나 예외 자체를 발생시킵니다. 나중에 값에 액세스해야하는 경우 .get () 또는 .value ()와 같은 메소드로 설정하십시오.

    _target에 대한이 코드는 약간 재 작성되어야합니다.

    def _target(queue, function, *args, **kwargs):
        try:
            queue.put((True, function(*args, **kwargs)))
        except:
            queue.put((False, exc_info())) # get *all* the exec info, don't do exc_info[1]
    
    # then later:
        raise exc_info[0], exc_info[1], exc_info[2]
    

    그렇게하면 스택 트레이스가 올바르게 보존되어 프로그래머에게 표시됩니다.

    저는 여러분이 유용한 라이브러리를 작성하는 데있어 합당한 첫 번째 균열을 만들었다 고 생각합니다. 저는 목표를 달성하기위한 처리 모듈의 사용법을 좋아합니다.

  2. ==============================

    2.이것은 Jerub이 언급 한 데코레이터 구문을 얻는 방법이다.

    이것은 Jerub이 언급 한 데코레이터 구문을 얻는 방법이다.

    def timeout(limit=None):
        if limit is None:
            limit = DEFAULT_TIMEOUT
        if limit <= 0:
            raise TimeoutError() # why not ValueError here?
        def wrap(function):
            return _Timeout(function,limit)
        return wrap
    
    @timeout(15)
    def mymethod(): pass
    
  3. ==============================

    3.페블 라이브러리는 문제가있는 로직을 처리 할 수있는 플랫폼 간 구현을 제공하도록 설계 되었기 때문에 충돌, 세그먼트 또는 무기한 실행이 가능합니다.

    페블 라이브러리는 문제가있는 로직을 처리 할 수있는 플랫폼 간 구현을 제공하도록 설계 되었기 때문에 충돌, 세그먼트 또는 무기한 실행이 가능합니다.

    from pebble import concurrent
    
    @concurrent.process(timeout=10)
    def function(foo, bar=0):
        return foo + bar
    
    future = function(1, bar=2)
    
    try:
        result = future.result()  # blocks until results are ready
    except Exception as error:
        print("Function raised %s" % error)
        print(error.traceback)  # traceback of the function
    except TimeoutError as error:
        print("Function took longer than %d seconds" % error.args[1])
    

    데코레이터는 정적 및 클래스 메서드에서도 작동합니다. 그럼에도 불구하고 나는 실수를 범하기 쉽기 때문에 방법을 꾸미는 것은 추천하지 않는다.

  4. from https://stackoverflow.com/questions/2196999/how-to-add-a-timeout-to-a-function-in-python by cc-by-sa and MIT license