Хорошая обработка исключений при повторной попытке кода - PullRequest
3 голосов
/ 28 августа 2009

У меня есть несколько тестовых случаев. Тестовые случаи опираются на данные, которые требуют времени для вычисления. Чтобы ускорить тестирование, я кэшировал данные, чтобы их не нужно было пересчитывать.

Теперь у меня есть foo(), который просматривает кэшированные данные. Я не могу сказать заранее, на что это будет смотреть, так как это во многом зависит от тестового примера.

Если тестовый случай не удался, потому что он не находит нужные кэшированные данные, я не хочу, чтобы он потерпел неудачу - я хочу, чтобы он вычислил данные и затем попытался снова. Я также не знаю, какое именно исключение вызовет пропущенные данные.

Мой код сейчас выглядит так:

if cacheExists:
    loadCache()
    dataComputed = False
else:
    calculateData()
    dataComputed = True

try:
    foo()
except:
    if not dataComputed:
        calculateData() 
        dataComputed = True
        try:
            foo()
        except:
            #error handling code
    else:
        #the same error handling code

Какой лучший способ реструктурировать этот код?

Ответы [ 5 ]

4 голосов
/ 28 августа 2009

Я не согласен с ключевым предложением в существующих ответах, которое в основном сводится к обработке исключений в Python, как, скажем, в C ++ или Java - это НЕ предпочтительный стиль в Python, где часто бывают старые добрые идеи, которые «лучше просить прощения, чем разрешения» (попытайтесь выполнить операцию и разберитесь с исключением, если оно есть, вместо того, чтобы затенять основной поток кода и нести накладные расходы путем тщательных предварительных проверок). Я согласен с Габриэлем в том, что голая except вряд ли когда-либо будет хорошей идеей (если все, что она делает, это какая-то форма регистрации, за которой следует raise, чтобы позволить распространению исключения). Итак, скажем, у вас есть кортеж со всеми типами исключений, которые вы ожидаете и хотите обрабатывать одинаково, скажем:

expected_exceptions = KeyError, AttributeError, TypeError

и всегда используйте except expected_exceptions:, а не голый except:.

Итак, с учетом этого, один немного менее повторяющийся подход к вашим потребностям:

try:
    foo1()
except expected_exceptions:
    try:
        if condition:
            foobetter()
        else:
            raise
    except expected_exceptions:
        handleError()

Другой подход заключается в использовании вспомогательной функции для переноса логики try / кроме:

def may_raise(expected_exceptions, somefunction, *a, **k):
  try:
    return False, somefunction(*a, **k)
  except expected_exceptions:
    return True, None

Такой помощник часто может оказаться полезным в нескольких различных ситуациях, поэтому довольно часто иметь что-то подобное где-то в «служебных» модулях проекта. Теперь для вашего случая (без аргументов, без результатов) вы можете использовать:

failed, _ = may_raise(expected_exceptions, foo1)
if failed and condition:
  failed, _ = may_raise(expected_exceptions, foobetter)
if failed:
  handleError()

Я бы сказал, что он более линейный и, следовательно, более простой. Единственная проблема этого общего подхода заключается в том, что вспомогательная функция, такая как may_raise, не заставляет вас так или иначе иметь дело с исключениями, поэтому вы могли бы просто забыть это сделать (так же, как и использование кодов возврата вместо исключений, указывающих на ошибки, склонен к ошибочным игнорированию этих возвращаемых значений); так что используйте его экономно ...! -)

1 голос
/ 28 августа 2009

Мне нравится альтернативный подход, предложенный Алексом Мартелли.

Что вы думаете об использовании списка функций в качестве аргумента may_raise. Функции будут выполняться до тех пор, пока одна из них не будет успешной!

Вот код

def foo(x):
    raise Exception("Arrrgh!")
    return 0

def foobetter(x):
    print "Hello", x
    return 1

def try_many(functions, expected_exceptions, *a, **k):
    ret = None
    for f in functions:
        try:
            ret = f(*a, **k)
        except expected_exceptions, e:
            print e
        else:
            break
    return ret

print try_many((foo, foobetter), Exception, "World")

результат

Arrrgh!
Hello World
1
1 голос
/ 28 августа 2009

Иногда нет хорошего способа выразить поток, это просто сложно. Но вот способ вызвать foo () только в одном месте и обработать ошибки только в одном месте:

if cacheExists:
    loadCache()
    dataComputed = False
else:
    calculateData()
    dataComputed = True

while True:
    try:
        foo()
        break
    except:
        if not dataComputed:
            calculateData()
            dataComputed = True
            continue 
        else:
            #the error handling code
            break

Тебе может не понравиться цикл, YMMV ...

Или:

if cacheExists:
    loadCache()
    dataComputed = False
else:
    calculateData()
    dataComputed = True

done = False
while !done:
    try:
        foo()
        done = True
    except:
        if not dataComputed:
            calculateData()
            dataComputed = True
            continue 
        else:
            #the error handling code
            done = True
1 голос
/ 28 августа 2009

Использование общих исключений обычно не очень хорошая идея. Какое исключение вы ожидаете там? Это KeyError, AttributeError, TypeError ...

После того, как вы определили, какой тип ошибки вы ищете, вы можете использовать что-то вроде hasattr() или оператор in или многие другие, которые проверят ваше состояние, прежде чем вы столкнетесь с исключениями.

Таким образом, вы можете очистить свой логический поток и сохранить обработку исключений для действительно сломанных вещей!

0 голосов
/ 28 августа 2009

Есть ли способ узнать, хотите ли вы сделать foobetter () перед выполнением вызова? Если вы получаете исключение, это должно быть потому, что произошло нечто неожиданное (исключительное!). Не используйте исключения для управления потоком.

...