почему Contextmanager выдает ошибку времени выполнения «генератор не остановился после throw()»?

В моей утилите.py у меня есть,

@contextmanager
def rate_limit_protection(max_tries=3, wait=300):
    tries = 0
    while max_tries > tries:
        try:
            yield
            break
        except FacebookRequestError as e:
            pprint.pprint(e)
            if e._body['error']['message'] == '(#17) User request limit reached':
                print("waiting...")
                time.sleep(wait)
                tries += 1

В моем task.py я вызываю:

for date in interval:
   with utility.rate_limit_protection():
      stats = account.get_insights(params=params)

После запуска задачи для заданного диапазона дат, когда срабатывает ограничение скорости Facebook, программа ждет 300 секунд, после чего происходит сбой с ошибкой.

File "/Users/kamal/.pyenv/versions/3.4.0/lib/python3.4/contextlib.py", line 78, in __exit__
    raise RuntimeError("generator didn't stop")
RuntimeError: generator didn't stop

person kamal patwa    schedule 13.01.2016    source источник
comment
Ожидаете ли вы, что этот менеджер контекста rate_limit_protection будет многократно запускать тело оператора with? with этого не делает. Тело работает один раз.   -  person user2357112 supports Monica    schedule 13.01.2016
comment
Нет, нет.. Существует цикл for, окружающий оператор with. оператор with запускается один раз для всего тела цикла for. Вы имеете в виду, что у меня не может быть цикла while внутри моей функции rate_limit_protection()?   -  person kamal patwa    schedule 14.01.2016
comment
Почему вы ожидаете, что оператор with будет выполняться один раз для всего цикла, если он находится внутри тела цикла?   -  person user2357112 supports Monica    schedule 14.01.2016
comment
for date in interval: with utility.rate_limit_protection(): stats = account.get_insights(params=params) Я отредактировал вопрос. Теперь вы можете получить некоторое представление о том, что я пытаюсь сделать.   -  person kamal patwa    schedule 14.01.2016
comment
Это почти то же самое, что и stackoverflow.com/q/29708445/245173.   -  person jpkotta    schedule 18.03.2016


Ответы (1)


Оператор with не является циклической конструкцией. Его нельзя использовать для многократного выполнения кода. Менеджер контекста, созданный с помощью @contextmanager, должен yield только один раз.

Контекстный менеджер делает (в основном) три вещи:

  1. Он запускает некоторый код перед блоком кода.
  2. Он запускает некоторый код после блока кода.
  3. При необходимости он подавляет исключения, возникающие в блоке кода.

Если вы хотите сделать что-то подобное, вам нужно переписать это так, чтобы цикл был вынесен за пределы контекстного менеджера, или чтобы контекстного менеджера вообще не было.

Одним из вариантов было бы написать функцию, которая принимает обратный вызов в качестве аргумента, а затем вызывает обратный вызов в цикле, подобном тому, который вы сейчас имеете в своем диспетчере контекста:

def do_rate_protection(callback, max_tries=3):
    tries = 0
    while max_tries > tries:
        try:
            callback()
            break
        except FacebookRequestError as e:
            # etc.

Затем вы можете назвать это так:

for date in interval:
    def callback():
        # code
    do_rate_protection(callback)

Если обратному вызову не нужна переменная date, вы можете переместить ее за пределы цикла, чтобы избежать повторного создания одной и той же функции (что приводит к расточительному расходу ресурсов). Вы также можете сделать date параметром функции callback() и передать его с помощью functools.partial.

person Kevin    schedule 14.01.2016