掩盖Python中的异常?

时间:2015-06-19 03:21:47

标签: python exception exception-handling

通常使用with语句打开文件,以便文件句柄不会泄露:

with open("myfile") as f:
    …

但是如果异常发生在 open调用中的某个地方怎么办? open函数很可能不是Python解释器中的原子指令,因此完全有可能在KeyboardInterrupt调用之前的某个时刻抛出异常异常,例如open。完成,但系统调用完成后。

处理此问题的常规方法(例如,在POSIX信号中)使用屏蔽机制:在屏蔽时,异常的传递将暂停,直到它们稍后被取消屏蔽。这允许以open之类的操作以原子方式实现。 Python中是否存在这样的原语?

[*]有人可能会说KeyboardInterrupt并不重要,因为程序即将死亡,但并非所有程序都是如此。可以想象,程序可能会选择在顶层捕获KeyboardInterrupt并继续执行,在这种情况下,泄漏的文件句柄会随着时间的推移而累加。

2 个答案:

答案 0 :(得分:1)

我认为不可能屏蔽exceptions,你可以屏蔽signals而不是exceptions。在您的情况下,KeyboardInterrupt是引发signal.SIGINT时引发的异常(即Ctrl + C)。

无法掩饰Exceptions,因为它没有意义,对吧?假设您正在打开('file','r'),但file不存在,这会导致open函数抛出IOError异常,我们应该无法mask这些例外情况。掩盖它是没有意义的,因为在上述情况下,open永远无法完成。

  

例外 - 需要特殊处理的异常或异常情况

对于KeyboardInterrupt异常,它的不同之处在于,就像我说的那样,它实际上是一个导致signal异常的KeyboardInterrupt

使用函数mask [Reference]

,从Python 3.3开始,只能在{1}}中发出信号

为此你必须将signal.pthread_sigmask移动到另一个块,以便我们可以像context expression这样的信号,运行上下文表达式来获取上下文管理器,然后{{1信号,示例代码看起来像(请注意我没有亲自测试过此代码) -

mask

答案 1 :(得分:0)

一些澄清:似乎异步异常在Python中并不常用。标准库仅记录KeyboardInterrupt AFAIK。其他库可以实现自己的via信号处理程序,但我不认为(或希望?)这是一种常见的做法,因为异步异常非常难以使用。

以下是无法工作的天真解决方案:

try:
    handle = acquire_resource(…)
except BaseException as e:
    handle.release()
    raise e
else:
    with handle:
        …
  • 异常处理部分仍然容易受到异常的影响:在捕获异常但KeyboardInterrupt完成之前,release可能会再次发生{。<}}

  • try语句的结尾与with语句的开头之间也存在“差距”,因为它容易受到异常的影响。

我认为无论如何都不能让它以这种方式运作。

从不同的角度思考,似乎异步异常的唯一方式来自信号。如果这是真的,可以将它们掩盖为@AnandSKumar suggested。但是,屏蔽不可移植,因为它需要pthreads。

尽管如此,我们可以用一点点诡计伪装掩饰:

def masking_handler(queue, prev_handler):
    def handler(*args):
        queue.append(lambda: prev_handler[0](*args))
    return handler

mask_stack = []

def mask():
    queue = []
    prev_handler = []
    new_handler = masking_handler(queue, prev_handler)
    # swap out the signal handler with our own
    prev_handler[0] = signal.signal(signal.SIGINT, new_handler)
    mask_stack.append((prev_handler[0], queue))

def unmask():
    prev_handler, queue = mask_stack.pop()
    # restore the original signal handler
    signal.signal(signal.SIGINT, prev_handler)
    # the remaining part may never occur if a signal happens right now
    # but that's fine
    for event in queue:
        event()

mask()
with acquire_resource(…) as handle:
    unmask()
    …

如果SIGINT是我们关心的唯一来源,这将有效。不幸的是,它会因多个信号而崩溃,不仅因为我们不知道正在处理哪些信号,还因为我们不能原子地换掉多个信号!