给出一个函数调用和一个紧随其后的try块,在任何情况下该调用都能正常返回,但引发了异常并且没有被try块捕获?
例如:
# example 1
resource = acquire_a_resource()
try:
resource.do_something()
# some more code...
finally:
resource.close()
acquire_a_resource()
是否可以正常返回,但不会调用resource.close()
?
换句话说,是否存在以下情况:
# example 2
resource = None
try:
resource = acquire_a_resource()
resource.do_something()
# some more code...
finally:
if resource:
resource.close()
比示例1更安全吗?
也许是因为与KeyboardInterrupt
/线程/信号有关?
答案 0 :(得分:4)
是的,至少在理论上是这样,尽管在CPython中不是(有关详细信息,请参见脚注)。线程并不是特别相关,但是您的KeyboardInterrupt场景恰恰是正确的:
resource = acquire_a_resource()
调用该函数。该函数获取资源并返回该句柄,然后在分配给变量 1 的过程中发生键盘中断。所以:
try:
不运行-发生KeyboardInterrupt
异常,保留当前函数并取消绑定变量。
第二个版本通过finally
子句,因此假设if resource
找到了布尔truth-y,resource.close()
就会被调用。
(请注意,实际上触发此操作通常非常困难:您必须将中断时间定为正确。您可以通过例如在time.sleep(1)
之前添加try
来大大增加比赛窗口。 )
在许多情况下,with
语句效果很好:
with acquire_a_resource() as resource:
resource.do_something()
close
内置到__exit__
方法中。即使通过异常转义了块,该方法也会运行。
1 通常,该实现必须完成将获取的资源与变量的绑定,否则将发生不可恢复的竞争。在CPython中,发生这种情况的原因是解释器检查语句之间的中断,并偶尔检查源代码中的重要位置。
CPython实际上增加了另一种特殊情况:
/* Do periodic things. Doing this every time through
the loop would add too much overhead, so we do it
only every Nth instruction. We also do it if
``pendingcalls_to_do'' is set, i.e. when an asynchronous
event needs attention (e.g. a signal handler or
async I/O handler); see Py_AddPendingCall() and
Py_MakePendingCalls() above. */
if (_Py_atomic_load_relaxed(&_PyRuntime.ceval.eval_breaker)) {
opcode = _Py_OPCODE(*next_instr);
if (opcode == SETUP_FINALLY ||
opcode == SETUP_WITH ||
opcode == BEFORE_ASYNC_WITH ||
opcode == YIELD_FROM) {
/* Few cases where we skip running signal handlers and other
pending calls:
- If we're about to enter the 'with:'. It will prevent
emitting a resource warning in the common idiom
'with open(path) as file:'.
- If we're about to enter the 'async with:'.
- If we're about to enter the 'try:' of a try/finally (not
*very* useful, but might help in some cases and it's
traditional)
- If we're resuming a chain of nested 'yield from' or
'await' calls, then each frame is parked with YIELD_FROM
as its next opcode. If the user hit control-C we want to
wait until we've reached the innermost frame before
running the signal handler and raising KeyboardInterrupt
(see bpo-30039).
*/
goto fast_next_opcode;
}
(Python/ceval.c
,第1000行附近)。
实际上,try
行确实在运行,因为这里有SETUP_FINALLY
。我还不清楚其他Python实现是否也做同样的事情。