2010-02-04 6 views
23

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

#! /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 <[email protected]>' 
__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.") 


편집 :이 코드는 파이썬 3.x를 위해 작성되었으며 장식으로 클래스 메소드를 위해 설계되지 않았습니다. multiprocessing 모듈은 프로세스 경계에서 클래스 인스턴스를 수정하도록 설계되지 않았습니다.

+0

예외 처리는 Python 3에서만 작동합니다.x는 원래 스택 트레이스를 버리고 "raise"에서 시작된 것으로 예외를 표시하며 스택 트레이스에는 어설 션이 전혀 표시되지 않습니다. –

답변

13

코드의 주된 문제점은 서브 클래스 화되지 않는 클래스에서 두 개의 밑줄 네임 스페이스 충돌 방지가 과도하게 사용된다는 것입니다.

일반적으로 self.__foo# This is a mixin and we don't want arbitrary subclasses to have a namespace conflict의 줄을 따라 주석을 수반해야하는 코드 냄새입니다.

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는 다음과 같습니다 :

또한이 방법의 클라이언트 API는 다음과 같이 보일 것이다 당신은 @property를 사용하는

@timeout(15) 
def mymethod(): pass 

try: 
    my_method() 
except TimeoutError: 
    pass 

을 당신의 클래스에서 무언가를 돌연변이하는 주범인데, 이것은 좋은 생각이 아닙니다. 예를 들어 .value가 두 번 액세스 될 때 어떤 일이 발생합니까? 대기열이 이미 비어 있기 때문에 queue.get()이 휴지통을 반환하므로 실패 할 것으로 보입니다.

@ 속성 만 완전히 제거하십시오. 이런 맥락에서 사용하지 마십시오. 사용 사례에 적합하지 않습니다. 을 호출하면 블록을 호출하고 값을 반환하거나 예외 자체를 발생시킵니다. 나중에 값에 액세스해야하는 경우 .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] 

스택 트레이스는 프로그래머에 정확하게 볼 유지됩니다 그런 식으로.

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

+0

이중 언더 스코어가 파이썬에서 개인 변수를 작성하는 유일한 방법은 아닌가? 개인용 변수는 실제 객체 지향 프로그래밍에서 선호되는데 이것이 캡슐화가 작동하는 방식입니다. – BillR

+0

@BillR : 파이썬에는 "실제"개인 변수가 없습니다. 클래스 외부에 밑줄 접미어가 붙은 클래스 이름이 맹 글링 된 경우를 제외하고는 비공개로 적용하기 위해 수행되는 작업이 없습니다. 작동 원리를 알면 쉽게 둘러 볼 수 있습니다. 이 모든 경우에도 객체 지향 코드를 사용하여 객체 지향 코드를 작성할 수 있으므로 모든 프로그래밍 언어에서 캡슐화를 강제하는 것은 필수 조건이 아닙니다. – martineau

6

이는 장식 구문을 얻는 방법이다 여룹는 Pebble 라이브러리가 문제가 논리 할 수 ​​crash, segfault or run indefinitely을 처리 할 수있는 크로스 플랫폼 구현을 제공하도록 설계되었습니다

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 
+0

이전에 데코레이터 구문을 사용했지만이 경우에는 권장하지 않습니다. –

+0

@NoctisSkytower 왜이 경우에는 데코레이터를 권장하지 않겠습니까? 불이익이나 위험은 무엇이라고 생각합니까? –

+0

@tristan : 대부분의 데코 레이팅 된 코드는 클래스의 메서드를 포함합니다. 이 예제에서 다중 처리가 작동하는 방식에 따라 데코 레이팅 된 코드에서 발생하는 모든 변경 사항은 원본 객체에 반영되지 않습니다. 모든 변경은'add_timeout' 함수가 생성을 끝내는 두 번째 프로세스에 남아 있습니다. –

1

을 언급했다.

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]) 

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

관련 문제