使用 Python 'with' 语句时捕获异常
- 2025-03-18 08:53:00
- admin 原创
- 82
问题描述:
我不知道如何处理 python 'with' 语句的异常。如果我有代码:
with open("a.txt") as f:
print f.readlines()
我确实想处理“文件未找到异常”以便做点什么。但我写不出来
with open("a.txt") as f:
print f.readlines()
except:
print 'oops'
并且不能写
with open("a.txt") as f:
print f.readlines()
else:
print 'oops'
将其包含with
在 try/except 语句中也不起作用,并且不会引发异常。我该怎么做才能with
以 Pythonic 方式处理语句内部的失败?
解决方案 1:
此解决方案将把 with-block-code 保留在 try-except-clause 之外。
try:
f = open('foo.txt')
except FileNotFoundError:
print('error')
else:
with f:
print f.readlines()
解决方案 2:
执行此操作的最佳“Pythonic”方式,即利用该语句,在PEP 343with
中列为示例#6 ,其中给出了该语句的背景。
from contextlib import contextmanager
@contextmanager
def opened_w_error(filename, mode="r"):
try:
f = open(filename, mode)
except IOError, err:
yield None, err
else:
try:
yield f, None
finally:
f.close()
使用方法如下:
with opened_w_error("/etc/passwd", "a") as (f, err):
if err:
print "IOError:", err
else:
f.write("guido::0:0::/:/bin/sh
")
解决方案 3:
使用 Python 'with' 语句时捕获异常
从 Python 2.6 开始, with 语句就可以在没有__future__
import 的情况下使用了。你可以从Python 2.5 开始使用它(但此时是时候升级了!):
from __future__ import with_statement
这是您最接近正确的内容。您几乎已经完成了,但with
没有except
子句:
with open("a.txt") as f: print(f.readlines()) except: # <- with doesn't have an except clause. print('oops')
上下文管理器的__exit__
方法,如果返回,False
则会在完成时重新引发错误。如果它返回True
,则会抑制它。open
内置方法__exit__
不会返回True
,因此您只需将其嵌套在 try, except 块中:
try:
with open("a.txt") as f:
print(f.readlines())
except Exception as error:
print('oops')
标准样板:不要使用一个空的except:
which 来捕获BaseException
所有其他可能的异常和警告。至少要像 一样具体Exception
,对于这个错误,也许可以捕获IOError
。只捕获您准备处理的错误。
因此在这种情况下,你应该这样做:
>>> try:
... with open("a.txt") as f:
... print(f.readlines())
... except IOError as error:
... print('oops')
...
oops
解决方案 4:
with
区分复合语句引发的异常的可能来源
区分语句中发生的异常with
很棘手,因为它们可能源自不同的地方。异常可能从以下任一位置引发(或其中调用的函数):
ContextManager.__init__
ContextManager.__enter__
正文
with
ContextManager.__exit__
有关更多详细信息,请参阅有关上下文管理器类型的文档。
如果我们想区分这些不同的情况,仅仅将包装成with
是try .. except
不够的。考虑以下示例(使用ValueError
作为示例,但当然可以用任何其他异常类型替换):
try:
with ContextManager():
BLOCK
except ValueError as err:
print(err)
这里except
将捕获源自所有四个不同位置的异常,因此无法区分它们。如果我们将上下文管理器对象的实例移到 之外with
,我们就可以区分__init__
和BLOCK / __enter__ / __exit__
:
try:
mgr = ContextManager()
except ValueError as err:
print('__init__ raised:', err)
else:
try:
with mgr:
try:
BLOCK
except TypeError: # catching another type (which we want to handle here)
pass
except ValueError as err:
# At this point we still cannot distinguish between exceptions raised from
# __enter__, BLOCK, __exit__ (also BLOCK since we didn't catch ValueError in the body)
pass
实际上,这只是对__init__
部分内容有帮助,但我们可以添加一个额外的标记变量来检查主体是否with
开始执行(即区分__enter__
和其他部分):
try:
mgr = ContextManager() # __init__ could raise
except ValueError as err:
print('__init__ raised:', err)
else:
try:
entered_body = False
with mgr:
entered_body = True # __enter__ did not raise at this point
try:
BLOCK
except TypeError: # catching another type (which we want to handle here)
pass
except ValueError as err:
if not entered_body:
print('__enter__ raised:', err)
else:
# At this point we know the exception came either from BLOCK or from __exit__
pass
棘手的部分是区分源自BLOCK
和的异常__exit__
,因为逃逸主体的异常with
将被传递给__exit__
可以决定如何处理它(参见文档)。但是,如果__exit__
引发自身,则原始异常将被新异常替换。为了处理这些情况,我们可以except
在主体中添加一个通用子句with
来存储任何可能被忽略的潜在异常,并将其与except
稍后在最外层捕获的异常进行比较 - 如果它们相同,则意味着起源是,BLOCK
否则它是__exit__
(如果__exit__
通过返回真值来抑制异常,则最外层except
将不会被执行)。
try:
mgr = ContextManager() # __init__ could raise
except ValueError as err:
print('__init__ raised:', err)
else:
entered_body = exc_escaped_from_body = False
try:
with mgr:
entered_body = True # __enter__ did not raise at this point
try:
BLOCK
except TypeError: # catching another type (which we want to handle here)
pass
except Exception as err: # this exception would normally escape without notice
# we store this exception to check in the outer `except` clause
# whether it is the same (otherwise it comes from __exit__)
exc_escaped_from_body = err
raise # re-raise since we didn't intend to handle it, just needed to store it
except ValueError as err:
if not entered_body:
print('__enter__ raised:', err)
elif err is exc_escaped_from_body:
print('BLOCK raised:', err)
else:
print('__exit__ raised:', err)
使用 PEP 343 中提到的等效形式的替代方法
PEP 343 — “with” 语句指定了该语句的等效“非 with”版本with
。在这里,我们可以轻松地用 包装各个部分try ... except
,从而区分不同的潜在错误源:
import sys
try:
mgr = ContextManager()
except ValueError as err:
print('__init__ raised:', err)
else:
try:
value = type(mgr).__enter__(mgr)
except ValueError as err:
print('__enter__ raised:', err)
else:
exit = type(mgr).__exit__
exc = True
try:
try:
BLOCK
except TypeError:
pass
except:
exc = False
try:
exit_val = exit(mgr, *sys.exc_info())
except ValueError as err:
print('__exit__ raised:', err)
else:
if not exit_val:
raise
except ValueError as err:
print('BLOCK raised:', err)
finally:
if exc:
try:
exit(mgr, None, None, None)
except ValueError as err:
print('__exit__ raised:', err)
通常更简单的方法就可以了
这种特殊异常处理的需求应该非常少见,通常将整个异常处理包装with
在一个try ... except
块中就足够了。特别是如果各种错误源由不同的(自定义)异常类型指示(需要相应地设计上下文管理器),我们就可以很容易地区分它们。例如:
try:
with ContextManager():
BLOCK
except InitError: # raised from __init__
...
except AcquireResourceError: # raised from __enter__
...
except ValueError: # raised from BLOCK
...
except ReleaseResourceError: # raised from __exit__
...
解决方案 5:
这是contextlib 文档针对 Python 3 的建议:
from contextlib import ExitStack
stack = ExitStack()
try:
f = stack.enter_context(open("a.txt"))
except FileNotFoundError:
print("oops")
else:
with stack:
f.readlines()
解决方案 6:
以下是来自 geeksforgeeks 的修改后的解决方案,其中带有描述:
class MessageWriter(object):
def __init__(self, file_name):
self.file_name = file_name
def __enter__(self):
try:
self.file = open(self.file_name, 'w')
except IOError:
print("An open exception occurred")
return self.file
def __exit__(self, *args):
self.file.close()
with MessageWriter('my_file.txt') as xfile:
try:
xfile.write('hello world')
except:
print("A write exception occurred")
解决方案 7:
这里的嵌套上下文非常清晰且易读:
@contextlib.contextmanager
def catch(err_types=Exception, on_exception=None):
try:
yield
except err_types as e:
if on_exception:
on_exception(e)
def on_missing_file(e):
print("Expected error")
with catch(FileNotFoundError, on_missing_file), open('test.txt') as f:
raise Exception("Unexpected error")
甚至可以通过额外的上下文(可选的yield示例)进行很好的扩展,或者on_missing_file
缩短为lambda。
扫码咨询,免费领取项目管理大礼包!