我不知道如何处理 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'
在 try/except 语句中包含 with
也不起作用,并且不会引发异常。为了以 Pythonic 方式处理 with
语句中的失败,我该怎么做?
with
语句不会神奇地破坏周围的 try...except
语句。
from __future__ import with_statement
try:
with open( "a.txt" ) as f :
print f.readlines()
except EnvironmentError: # parent of IOError, OSError *and* WindowsError where available
print 'oops'
如果您想要对公开调用与工作代码中的错误进行不同的处理,您可以执行以下操作:
try:
f = open('foo.txt')
except IOError:
print('error')
else:
with f:
print f.readlines()
利用 with
语句的最佳“Pythonic”方法被列为 PEP 343 中的示例 #6,它给出了语句的背景。
@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\n")
try...except
语句。
在使用 Python 'with' 语句时捕获异常
with 语句在没有 __future__
导入 since Python 2.6 的情况下可用。您可以将其作为 early as Python 2.5 (但此时是升级的时候了!):
from __future__ import with_statement
这是您所拥有的最接近纠正的东西。您快到了,但 with
没有 except
子句:
with open("a.txt") as f: print(f.readlines()) except: # <- with 没有 except 子句。打印('哎呀')
上下文管理器的 __exit__
方法,如果它返回 False
,它将在完成时重新引发错误。如果它返回 True
,它将抑制它。内置 open
的 __exit__
不返回 True
,因此您只需将它嵌套在 try 中,除了块:
try:
with open("a.txt") as f:
print(f.readlines())
except Exception as error:
print('oops')
和标准样板:不要使用捕获 BaseException
和所有其他可能的异常和警告的裸 except:
。至少与 Exception
一样具体,对于这个错误,也许可以捕获 IOError
。只捕获您准备处理的错误。
所以在这种情况下,你会这样做:
>>> try:
... with open("a.txt") as f:
... print(f.readlines())
... except IOError as error:
... print('oops')
...
oops
区分从复合 with 语句引发的异常的可能来源
区分 with
语句中发生的异常是很棘手的,因为它们可能起源于不同的地方。可以从以下任一位置(或其中调用的函数)引发异常:
上下文管理器.__init__
上下文管理器.__enter__
的身体
上下文管理器.__exit__
有关更多详细信息,请参阅有关 Context Manager Types 的文档。
如果我们想区分这些不同的情况,仅仅将 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__
,它可以决定如何处理它(请参阅 the docs)。但是,如果 __exit__
引发自身,则原始异常将被新异常替换。为了处理这些情况,我们可以在 with
的主体中添加一个通用 except
子句来存储任何可能会在不被注意的情况下逃脱的潜在异常,并将其与稍后在最外面的 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 -- The "with" Statement 指定 with
语句的等效“non-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__
...
我在我制作的程序中使用了它:
try:
with open(os.path.join(basedir, "rules.txt")) as f:
self.rules.setText(f.read())
except FileNotFoundError:
self.rules.setText("Sorry, unable to read rules")
OSError
用于 Python 3.3+。如图here:3.3版更改:EnvironmentError、IOError、WindowsError、socket.error、select.error和mmap.error已合并到OSError中,构造函数可能返回子类。