2016-04-20 5 views
1

Я пытаюсь написать небольшой менеджер контекста, который будет пытаться выполнить код несколько раз, пока код не будет работать или пока не будет сделано определенное количество попыток. Я попытался написать это, но я встречая трудности с наличием менеджера контекста справиться с проблемами, когда получают:Как менеджер контекстов Python может выполнить код?

Exception RuntimeError: 'generator ignored GeneratorExit' 

Как я должен закодировать это?

import contextlib 
import random 

def main(): 

    with nolube(): 
     print(1/random.randint(0, 1)) 

@contextlib.contextmanager 
def nolube(
    tries = None # None: try indefinitely 
    ): 
    """ 
    Create a context for trying something repeatedly. 
    """ 
    tries_done = 0 
    rekt = True 
    if tries is None: 
     while rekt is True: 
      try: 
       yield 
       rekt = False 
      except: 
       tries_done += 1 
       pass 
    else: 
     while rekt is True and tries_done <= tries: 
      try: 
       yield 
       rekt = False 
      except: 
       tries_done += 1 
       pass 

if __name__ == "__main__": 
    main() 

ответ

3

@contextlib.contextmanager имеет очень четкий договор; он будет возобновлен только . Он не может использоваться для повторного запуска кода.

Фактически, вы не можете использовать диспетчер контекстов для управления повторениями вообще. Вам нужен цикл здесь, а не менеджер контекста. Менеджер контекста не контролирует блок, он информируется только при входе и выходе.

Использовать вместо этого tenacity package*; он обеспечивает декоратора. Декоратор wraps a function in a while True loop, который заново запустит эту функцию для вас.

Вы бы применить его к делу, перемещая print() заявление в функцию, украшенную @retry, затем вызвать эту функцию:

import random 
from tenacity import retry 

@retry 
def foo(): 
    print(1/random.randint(0, 1)) 

def main(): 
    foo() 

* Этот ответ первоначально было рекомендовано retrying package но это было forked into a new package with updated API, когда этот проект упал в бездействии.

+0

Пакет 'retrying' не обновлялся какое-то время. Но есть вилка, называемая [упорство] (https://pypi.python.org/pypi/tenacity), которая поддерживается. – TimB

+0

@TimB: спасибо за хедз-ап, я отредактировал ответ, чтобы рекомендовать упорство. –

1

Короткий ответ: вы не можете сделать это с помощью контекстного менеджера в Python.

Мы можем только yield один раз в менеджере контекста, поэтому не имеет смысла использовать yield внутри цикла while. Это отличается от yield, используемого в Rubyblock s.

И у нас нет доступа к телу кода, например, мы не получаем автоматически что-то вроде function, которое мы можем использовать повторно.

Итак, нет, если вы хотите реализовать логику многократного использования retry, используйте вместо нее функцию.

def retry(func, n_times=None): 
    i = 1 
    while True: 
     try: 
      return func() 
     except Exception: 
      i += 1 
      if n_times and i >= n_times: 
       raise 
2

Вы не можете этого сделать. Менеджер контекста в Python это просто протокол, который:

  1. вызовы __enter__
  2. Выполняет один или несколько операторов
  3. вызовы __exit__

Пункт 3. гарантированно произойдет, что делает его отлично для обработки освобождения ресурсов и т. д. Но важным моментом здесь является пункт 2. Менеджер контекста запускает код в пределах контекста, затем обрабатывает 3. К этому моменту завершенный код не работает, для получить и недостижимо навсегда, поэтому вы не можете «снова называть это».contextlib предлагает хороший API для определения вашего менеджера контекста просто делая его как функция:

@contextmanager 
def ctxt(): 
    # 1: __enter__ code 
    yield 
    # 3: __exit__ code 

И документацию ясно specifies:

Функции украшается должен возвращать генератор-итератор при вызове. Этот итератор должен давать ровно одно значение, которое будет привязано к целям в предложении as с предложением, если оно есть.

Таким образом, предыдущий пункт остается.

Что вы можете сделать, чтобы назвать что-то repeteadly, чтобы поставить его в функцию, и украсит что функцию с «повторить до успеха» логика:

def dec(f): 
    def decorated(*args, **kwargs): 
     while True: 
      try: 
       return f(*args, **kwargs) 
      except: 
       pass 
    return decorated 

Но это совершенно не связано с контекстных менеджеров.