У меня есть ситуация, когда я хочу сделать несколько вещей при обработке исключения. Поскольку я хочу рассказать об общем случае, я переведу свой конкретный случай на более общий язык.

Когда у меня есть исключение в этом куске кода, я хочу:

  1. Всегда выполняйте операцию в стиле отката
  2. Если это исключение для конкретного приложения, я хочу выполнить регистрацию и проглотить исключение.

Так что я могу придумать два пути решения этой проблемы, оба безобразно:

# Method nested-try/except block
try:
    try:
        do_things()
    except:
        rollback()
        raise
except SpecificException as err:
    do_advanced_logging(err)
    return
# Method Duplicate Code
try:
    do_things()
except SpecificException as err:
    rollback()
    do_advanced_logging(err)
    return
except:
    rollback()
    raise

Оба будут иметь одинаковое поведение.

Я склоняюсь к вложенным попыткам / кроме решения самостоятельно. Хотя это может быть немного медленнее, я не думаю, что разница в скорости здесь уместна - по крайней мере, не для моего конкретного случая. Дублирование кода - это то, чего я хочу избежать, потому что мой оператор rollback () несколько более сложен, чем просто откат базы данных, даже если он имеет точно такую же цель (он включает веб-API).

Есть третий вариант, который я не заметил, что лучше? Или метод дублированного кода лучше? Обратите внимание, что функциональность rollback () уже разобрана в максимально возможной степени, но все еще содержит вызов функции и три аргумента, которые включают одну жестко закодированную строку. Поскольку эта строка уникальна, нет причин делать ее именованной константой.

2
Gloweye 25 Июн 2019 в 11:09

3 ответа

Лучший ответ

Как насчет проверки типа экземпляра исключения в коде?

# Method .. No Duplicate Code
try:
    do_things()
except Exception as e:
    rollback()
    if isinstance(e, SpecificException):
        do_advanced_logging(e)
        return
    raise
7
smassey 25 Июн 2019 в 08:15

Вы можете написать менеджер контекста:

import random

class SpecificException(Exception):
    pass

def do_things(wot=None):
    print("in do_things, wot = {}".format(wot))
    if wot:
        raise wot("test")


def rollback():
    print("rollback")


def do_advance_logging(exc_type, exc_val, traceback):
    print("logging got {} ('{}')".format(exc_type, exc_val))


class rollback_on_error(object):
    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, traceback):
        # always rollback
        rollback()
        # log and swallow specific exceptions
        if exc_type and issubclass(exc_type, SpecificException):
            do_advance_logging(exc_type, exc_val, traceback)
            return True
        # propagate other exceptions
        return False


def test():
    try:
        with rollback_on_error():
            do_things(ValueError)
    except Exception as e:
        print("expected ValueError, got '{}'".format(type(e)))
    else:
        print("oops, should have caught a ValueError")

    try:
        with rollback_on_error():
            do_things(SpecificException)
    except Exception as e:
        print("oops, didn't expect exception '{}' here".format(e))
    else:
        print("ok, no exception")


    try:
        with rollback_on_error():
            do_things(None)
    except Exception as e:
        print("oops, didn't expect exception '{}' here".format(e))
    else:
        print("ok, no exception")



if __name__ == "__main__":
    test()

Но если у вас нет десятков вхождений этого паттерна, я бы предпочел придерживаться очень очевидных и совершенно питонических решений - либо вложенных обработчиков исключений, либо явной проверки типов (isinstance) в предложении исключением.

2
bruno desthuilliers 25 Июн 2019 в 09:03

Как насчет помещения отката в предложение finally? что-то вроде:

do_rollback = True
try:
    do_things()
    do_rollback = False
except SpecificException as err:
    do_advanced_logging(err)
finally:
    if do_rollback:
        rollback()

Альтернатива - использовать предложение else, которое позволит вам сделать больше в неисключительном случае и не будет иметь исключений, пойманных в одном месте:

do_rollback = True
try:
    do_things()
except SpecificException as err:
    do_advanced_logging(err)
else:
    record_success()
    do_rollback = False
finally:
    if do_rollback:
        rollback()

Полезно, когда record_success может поднять SpecificException, но вы не хотите do_advanced_logging

4
Sam Mason 25 Июн 2019 в 08:45