基于公共lisp的重启条件系统的pythonisation
withrestart的Python项目详细描述
withrestart:使用命名的重新启动函数进行结构化错误恢复
这是对 基于重启的通用lisp状态系统。它是用来制造错误的 通过消除未处理错误的假设,恢复变得越来越简单 一定是致命的。
“restart”表示用于恢复函数执行的命名策略 错误发生后。在执行期间的任何时候 函数可以将restart对象推送到其调用堆栈上。如果有例外 在重新启动的范围内发生,调用链中较高的代码可以 调用它以从错误中恢复并让函数继续执行。 通过提供几个重新启动,函数可以提供几个不同的策略 从错误中恢复。
“处理程序”表示处理事件的更高级别策略 一个错误。它在概念上类似于“except”子句 建立一套处理程序对象,如果在 一些代码的执行。然而,有一个关键的区别:处理者 在不展开调用堆栈的情况下执行。因此他们有机会 采取纠正措施,然后恢复执行任何功能 引发了错误。
例如,考虑一个函数,它从 目录到内存中的dict中:
def readall(dirname): data = {} for filename in os.listdir(dirname): filepath = os.path.join(dirname,filename) data[filename] = open(filepath).read() return data
如果在调用os.listdir()之后丢失了其中一个文件,则 后续的open()将引发ioerror。我们可以抓住并处理 此函数中有错误,应该采取什么措施?应该 丢失的文件会被忽略吗?是不是应该用 一些默认内容?如果在 数据字典?什么价值?readAll()函数没有足够的 决定适当恢复策略的信息。
相反,readall()可以提供infrastructure来执行错误恢复 最后的决定权在呼叫代码上。以下定义 使用三个预定义的重新启动来让调用代码(a)跳过丢失的 文件完全,(2)在采取一些纠正措施后重试对open()的调用 操作,或(3)使用其他值代替丢失的文件:
def readall(dirname): data = {} for filename in os.listdir(dirname): filepath = os.path.join(dirname,filename) with restarts(skip,retry,use_value) as invoke: data[filename] = invoke(open,filepath).read() return data
这里需要注意的是使用“with”语句来建立一个新的上下文 在重新启动的范围内,并在调用 可能失败的函数。后者允许重新启动以注入备用 失败函数的返回值。
下面是如果调用代码想悄悄跳过 缺少文件:
def concatenate(dirname): with Handler(IOError,"skip"): data = readall(dirname) return "".join(data.itervalues())
这会将处理程序实例推送到执行上下文中,执行上下文将检测 ioerror实例并通过调用“skip”重启点进行响应。如果这个 处理程序是响应ioerror调用的,执行readAll() 函数将在“with restarts(…)”块之后立即继续。
请注意,无法使用 普通的try-except块;当ioerror传播到 concatenate()函数用于处理,执行 readAll()将被解除绑定,无法继续。
调用希望重新创建丢失文件的代码只需将 不同的错误处理程序:
def concatenate(dirname): def handle_IOError(e): open(e.filename,"w").write("MISSING") raise InvokeRestart("retry") with Handler(IOError,handle_IOError): data = readall(dirname) return "".join(data.itervalues())
通过提升invokerestart,这个处理程序将控制权传递回restart 由readAll()函数建立的。这次重新启动 将重新执行失败的函数调用,并让readAll()继续其 操作。
调用希望使用特殊sentinel值的代码将使用处理程序 要将所需值传递给“使用值”,请重新启动:
def concatenate(dirname): class MissingFile: def read(): return "MISSING" def handle_IOError(e): raise InvokeRestart("use_value",MissingFile()) with Handler(IOError,handle_IOError): data = readall(dirname) return "".join(data.itervalues())
通过将从错误中恢复的低级细节从 采取什么行动的高层战略有可能创造出 强大的恢复机制。
虽然此模块提供了一些预构建的重新启动,但函数将 通常都想自己创造。这可以通过传递回调来完成 进入重新启动对象构造函数:
def readall(dirname): data = {} for filename in os.listdir(dirname): filepath = os.path.join(dirname,filename) def log_error(): print "an error occurred" with Restart(log_error): data[filename] = open(filepath).read() return data
或者使用decorator定义内联重新启动:
def readall(dirname): data = {} for filename in os.listdir(dirname): filepath = os.path.join(dirname,filename) with restarts() as invoke: @invoke.add_restart def log_error(): print "an error occurred" data[filename] = open(filepath).read() return data
处理程序也可以使用类似的语法内联定义:
def concatenate(dirname): with handlers() as h: @h.add_handler def IOError(e): open(e.filename,"w").write("MISSING") raise InvokeRestart("retry") data = readall(dirname) return "".join(data.itervalues())
最后,是免责声明。我从来没有写过任何普通的口齿不清。我只看过 关于常见的lisp条件系统以及它有多棒。我肯定有 有很多事情是它可以做的,而这个模块根本做不到。例如:
- Since this is built on top of a standard exception-throwing system, the handlers can only be executed after the stack has been unwound to the most recent restart context; in Common Lisp they’re executed without unwinding the stack at all.
- Since this is built on top of a standard exception-throwing system, it’s probably too heavyweight to use for generic condition signalling system.
然而,当你看到一个好主意的时候,捏一个好主意并不羞耻……