Best way to roll back actions when an exception is raised with Python
我有一个这样的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | # step 1 remove from switch for server in server_list: remove_server_from_switch(server) logger.info("OK : Removed %s", server) # step 2 remove port for port in port_list: remove_ports_from_switch(port) logger.info("OK : Removed port %s", port) # step 3 execute the other operations for descr in pairs: move_descr(descr) # step 4 add server back to switch for server in server_list: add_server_to_switch(server) logger.info("OK : server added %s", server) # step 5 add back port for port in port_list: add_ports_to_switch(port) logger.info("OK : Added port %s", port) |
for循环中的函数可以引发异常,或者用户可以使用ctrl+c中断脚本。但是,如果在执行过程中引发异常,我希望通过撤消之前已经完成的更改来进入回滚模式。我的意思是,如果在步骤3中出现异常,我必须回滚步骤1和2(通过执行步骤4和5中的操作)。或者,如果用户试图在步骤1的for循环中间使用ctrl+c停止脚本,我希望回滚该操作并添加已删除的服务器。
除了使用例外情况,如何才能用一种好的Python式的方式来做呢?:)
这就是上下文管理器的作用。详细阅读WITH语句,但总的来说,您需要编写上下文管理器类,其中
1 2 3 4 | with RemoveServers(server_list): with RemovePorts(port_list): do_stuff # exiting the with blocks will undo the actions |
也许这样的方法会奏效:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | undo_dict = {remove_server_from_switch: add_server_to_switch, remove_ports_from_switch: add_ports_to_switch, add_server_to_switch: remove_server_from_switch, add_ports_to_switch: remove_ports_from_switch} def undo_action(action): args = action[1:] func = action[0] undo_dict[func](*args) try: #keep track of all successfully executed actions action_list = [] # step 1 remove from switch for server in server_list: remove_server_from_switch(server) logger.info("OK : Removed %s", server) action_list.append((remove_server_from_switch, server)) # step 2 remove port for port in port_list: remove_ports_from_switch(port) logger.info("OK : Removed port %s", port) action_list.append((remove_ports_from_switch, port)) # step 3 execute the other operations for descr in pairs: move_descr(descr) # step 4 add server back to switch for server in server_list: add_server_to_switch(server) logger.info("OK : server added %s", server) action_list.append((add_server_to_switch, server)) # step 5 add back port for port in port_list: add_ports_to_switch(port) logger.info("OK : Added port %s", port) action_list.append((add_ports_to_switch, port)) except Exception: for action in reverse(action_list): undo_action(action) logger.info("ERROR Recovery : undoing {func}({args})",func = action[0], args = action[1:]) finally: del action_list |
编辑:正如Tzaman下面所说,在这种情况下,最好的做法是将整个事情包装成一个上下文管理器,并使用
下面是它可能的样子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | class ActionManager(): def __init__(self, undo_dict): self.action_list = [] self.undo_dict = undo_dict def action_pop(self): yield self.action_list.pop() def action_add(self, *args): self.action_list.append(args) def undo_action(self, action): args = action[1:] func = action[0] self.undo_dict[func](*args) def __enter__(self): return self def __exit__(self, type, value, traceback): for action in self.action_stack: undo_action(action) logger.info("Action Manager Cleanup : undoing {func}({args})",func = action[0], args = action[1:]) |
现在你可以这样做了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #same undo_dict as before with ActionManager(undo_dict) as am: # step 1 remove from switch for server in server_list: remove_server_from_switch(server) logger.info("OK : Removed %s", server) am.action_add(remove_server_from_switch, server) # step 2 remove port for port in port_list: remove_ports_from_switch(port) logger.info("OK : Removed port %s", port) am.action_add(remove_ports_from_switch, port) # step 3 execute the other operations for descr in pairs: move_descr(descr) # steps 4 and 5 occur automatically |
另一种方法是在
1 2 3 | with SwitchManager(servers, ports) as pairs: for descr in pairs: move_descr(descr) |