Поддержание потока кода с возможностью отката в Python без экстремальной идентификации

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

Лучшее подходящее решение, которое мне пришло в голову, - это рабочий процесс Python try-finally, но он довольно проблематичен, когда последовательность команд длинная:

try:
    # perform action
    try:
        # perform action
        try: 
            # ...
        finally:
            # unroll
    finally:
        # unroll
finally:
    # unroll

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

Большинство попаданий, которые я получил при поиске "транзакций" и "откатов", были связаны с БД и не очень хорошо подходили для более общего вида кода... У кого-нибудь есть хорошая идея относительно того, как систематически сгладить это злодеяние?

1 ответ

Решение

Разве объекты Context Manager и оператор with не улучшат ситуацию? Особенно, если вы можете использовать версию Python, где оператор with поддерживает несколько выражений контекста, например 2.7 или 3.x. Вот пример:

class Action(object):
    def __init__(self, count):
        self.count = count
    def perform(self):
        print "perform " + str(self.count)
        if self.count == 2:
            raise Exception("self.count is " + str(self.count))
    def commit(self):
        print "commit " + str(self.count)
    def rollback(self):
        print "rollback " + str(self.count)
    def __enter__(self):
        perform()
        return self
    def __exit__(self, exc_type, exc_value, traceback):
        if exc_value is None:
            self.commit()
        else:
            self.rollback()

with Action(1), Action(2), Action(3):
    pass

Вы должны будете переместить свой код в набор "транзакционных" классов, таких как Action выше, где действие, которое должно быть выполнено, выполняется в __enter__() метод и, если это заканчивается нормально, вам будет гарантировано, что соответствующий __exit()__ метод будет вызван.

Обратите внимание, что мой пример не совсем соответствует вашему; вам придется настроить, что выполнить в __enter__() методы и что выполнять в with тело заявления. В этом случае вы можете использовать следующий синтаксис:

with Action(1) as a1, Action(2) as a2:
    pass

Чтобы иметь возможность получить доступ к Action объекты изнутри тела with заявление.

Другие вопросы по тегам