Почему Contextmanager генерирует ошибку времени выполнения "генератор не остановился после throw()"?
В моей утилите у меня есть
@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
1 ответ
with
оператор не является циклической конструкцией. Его нельзя использовать для повторного выполнения кода. Менеджер контекста, созданный с @contextmanager
должен только yield
один раз.
Менеджер контекста делает (в основном) три вещи:
- Он запускает некоторый код перед блоком кода.
- Он запускает некоторый код после блока кода.
- Необязательно, он подавляет исключения, возникающие в блоке кода.
Если вы хотите сделать что-то подобное, вам нужно переписать это так, чтобы цикл перемещался за пределы диспетчера контекста или чтобы вообще не было диспетчера контекста.
Одним из вариантов может быть написание функции, которая принимает обратный вызов в качестве аргумента, а затем вызывает обратный вызов в цикле, подобном тому, который вы сейчас используете в вашем диспетчере контекста:
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
,