Поддержание потока кода с возможностью отката в 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
заявление.