Блог им. Albus

Обработка таймаута на Питоне

Коллеги, помогите написать красиво кусочек кода. Обработка ситуации, когда ты заказал котировки с Финама, они не пришли, и ты пробуешь ещё раз. Сейчас я кривенько (чтобы описать задачу) написал так:

try: 
        txt=urlopen(url, timeout=20).readlines()
except timeout:
        print ("Exception!\nWait...")
        sleep (20)
        try:
                txt=urlopen(url, timeout=20).readlines()
        except timeout:
                print ("Exception!\nWait...")
                sleep (20)
                try:
                        txt=urlopen(url, timeout=20).readlines()
                except timeout:
                        print ("Котировки с Финама не пришли")
Как это написать красиво внутри цикла?
10 попыток. Если 10-я неудачная, выводим сообщение
print ("Котировки с Финама не пришли")
★4
Псевдокод:

MAX_ATTEMPTS = 10
attempts = 0
done = FALSE
while (!done && (attempts < MAX_ATTEMPTS)) {
  attempts++
  data = requestData(…)
  if (data != NULL) {
    done = TRUE
  }
}
if (!done) {
  error(«Failed to acquire data»)
}

requestData = function (…) {
  try {
    …
  } except {
    return NULL
  }
}
avatar

Eugene Logunov

Eugene Logunov,
не нужно одновременно и false и NULL использовать.
Можно просто done = NULL
исправить while
и убрать
  if (data != NULL) {
    done = TRUE
avatar

_sg_

def get_data(url, timeout, tries=0, max_tries=10):
  try:
    txt = urlopen(url, timeout=timeout).readlines()
  except TimeoutError:
    print(f«Retry {tries + 1} for {url}»)
    if tries + 1 <= max_tries:
       txt = get_data(url, timeout, tries + 1, max_tries)
    else:
      raise TimeoutError
  return txt
avatar

Михаил

Михаил, круто! С рекурсией! Я бы так не смог ))))
Михаил, так вроде тоже можно:



Albus (Игорь Китаев), можно естественно миллионом способов. Вопрос, как в некотором смысле правильнее для долгосрочной поддержки программы. 

Лучше делить программу на очень небольшие функции.

В Питоне цикл по range обычно признак криво написанной программы.

Создание флагов перед циклом тоже обычно антипатерн. 

Захотите вы потом поменять время ожидания или количество попыток — вам прийдется читать код и менять в нескольких местах. У меня вы просто вызов функции с другими параметрами сделаете.
avatar

Михаил

Михаил, все отлично

только вот вызывать рекурсивно функцию да еще много раз в exception не очень здорово

Инженер по контролю качества ПО наверняка придерется и не пропустит в продакшн
avatar

_sg_

_sg_, в питоне цикл с помощью exception реализован — возможно в других языках это и не очень хорошо. 
avatar

Михаил

Михаил, 
вызывать эту функцию так?
get_data(url, timeout=60, tries=0, max_tries=10)
tries = 0 — уточняю из-за неё потому что она тут глубоко участвует в рекурсии. 
Albus (Игорь Китаев), при вызове вы передаете url и timeout — остальные параметры не используете get_data(url, 60). 
avatar

Михаил

Михаил, 
спасибо, работает.
Только с этой строчкой что то не то, я её заменил
    print(f«Retry {tries + 1} for {url}»)
(правильные кавычки "" тоже ставил — безрезультатно)
Albus (Игорь Китаев), кавычки smartlab автоматически заменяет. f-строки могут не работать на старой версии Питона — у вас какая?
avatar

Михаил

Михаил, 3.8.2 — самая свежая
Albus (Игорь Китаев), тогда не знаю — должно работать с 3.6. Дистанционно сложно разобраться. 
avatar

Михаил

Albus (Игорь Китаев), это самый правильный и читабельный способ, очевидно
avatar

_xXx_

И так сойдёт! Ты же трейдер, а не программист! ))
рекурсия нужна )
avatar

siesta00

рекурсию поюзать
avatar

tashik

pypi.org/project/retrying/ если хочется прям красиво сделать.

а так в цикле считать попытки и брейкать цикл при удаче.

avatar

day0markets

day0markets, +1
сделать отдельно функцию запроса которая считывает данные и возвращает сами данные и флаг 1\0 — (есть ошибка получения данных/ нет ошибки)
выглядит на питоне так
for ...
 data, err = LoadData(параметры запроса)
 if err == 0:
   break
 else:
   timeout ()


avatar

sis12qw

sis12qw, ну такое. Имхо, вызывающая функция не должна знать о таймаутах и прочем. Ну и когда потребуется ее дернуть в другом придется лезть и смотреть что она там за флаги возвращает и опять городить огород с retry. Флаги вообще далеко не best practice, лучше exception выбрасывать и его обрабатывть
avatar

day0markets

def get_data(url, timeout=10, max_tries=10):<br>    data = None<br>    try:<br>        data = urlopen(url, timeout=timeout).readlines()<br>    except TimeoutError:<br>        max_tries -= 1<br>    if data:<br>        return data<br>    if max_tries == 0:<br>        return data<br>    get_data(url, timeout, max_tries)
avatar

Сергей


....все тэги
2010-2020
UPDONW