使用Python 'with'声明

我不知道如何处理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也不起作用,并且不会引发异常。我能做什么来处理with语句中的失败在Pythonic的方式?

281342 次浏览
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()

最好的“Pythonic”方法是利用with语句,在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")

在使用Python 'with'语句时捕获异常

with语句可以在没有__future__导入Python 2.6以来的情况下使用。你可以通过以下方式获得早在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中嵌套它,除了block:

try:
with open("a.txt") as f:
print(f.readlines())
except Exception as error:
print('oops')

标准样板:不要使用裸except:来捕获BaseException和其他所有可能的异常和警告。至少要像Exception一样具体,对于这个错误,可能要捕获IOError。只捕捉准备好处理的错误。

在这种情况下,你会这样做:

>>> try:
...     with open("a.txt") as f:
...         print(f.readlines())
... except IOError as error:
...     print('oops')
...
oops

区分复合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__2)。然而,如果__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——“with”语句指定了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")