How to manipulate the exception in __exit__ of a context manager?
我知道从上下文管理器的__exit__()方法中重新引发异常是一种糟糕的风格。所以,我想在实例上附加一个属性,它可以携带上下文信息,如果我让异常通过或者捕获它,这些信息是不可用的。这将避免重新提升它。
将属性附加到异常上的另一种方法是吞咽异常,在作为相关上下文管理器的实例上设置一些状态,然后检查该状态。问题是这会导致第22条军规,不是吗?因为异常意味着在with块内的执行被退出。除了再次进入with块外,无法重复该操作,对吗?因此,当__exit__()方法返回时,我试图存储上下文信息的实例将消失。
简而言之:当使用__exit__()方法时,如何处理待处理的实际异常(如果是,我将假定为针对这个问题给出的异常)?
- 谁说这是不好的款式?上下文管理器是转换异常的一种很好的方法。
- @MartijnPieers:实际上,文档说明它的样式不好,因为它会导致嵌套上下文管理器出现问题。是的,对于转换和不嵌套,这是很有意义的。
- 您不应该重新引发相同的异常(只返回None就可以确保保持异常状态),但是您可以引发一个新的异常。
- @马蒂:的确。但例外情况很好,只是我需要在呼叫链上传递一些信息。所以这就是为什么在我的情况下,重新养大会是不好的方式。
上下文管理器不会因为块退出而消失。您可以通过两种方式保存它:
首先创建上下文管理器,将其分配给变量,然后将with与该对象一起使用:
1 2 3 4 5
| cm = ContextManager()
with cm:
# ....
state = cm.attribute |
从__enter__方法返回上下文管理器本身,使用with ... as ...将其绑定到本地名称。当with退出时,该名称不会解除绑定:
1 2 3 4
| with ContextManager as cm:
# ....
state = cm.attribute |
其中ContextManager.__enter__使用return self。
您还可以对异常本身设置额外的属性;无需重新引发异常:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| >>> class ContextManager(object):
... def __enter__(self):
... return self
... def __exit__(self, tp, v, tb):
... if tp is None: return
... v.extra_attribute = 'foobar'
... self.other_extra_attribute = 'spam-n-ham'
...
>>> try:
... with ContextManager() as cm:
... raise ValueError('barfoo')
... except ValueError as ex:
... print vars(ex)
...
{'extra_attribute': 'foobar'}
>>> vars(cm)
{'other_extra_attribute': 'spam-n-ham'} |
在这里,异常被赋予了一个额外的属性,该属性一直持续到异常处理程序。在上面,我还表明cm仍然与上下文管理器绑定。
- 我的假设是你的v.extra_attribute = 'foobar'和我的setattr(exc_value, 'extra_attribute', 'foobar')在功能上是等效的。但是,使用它可以得到AttributeError: 'str' object has no attribute 'extra_attribute'。为什么我看到的值是str实例而不是异常实例?它在__exit__()方法中。
- 是否引发字符串异常?这是非常不赞成的。仅使用对象异常(因此BaseException的子类)。
- 如果您无法控制引发哪些异常,则无法对那些属于旧样式字符串异常的异常设置属性。标准库肯定不会再提高这些,但我不知道您在这里使用的是什么系统。-)
- 最后,字符串异常支持仅从Python2.6中删除,所以如果您在2.6或2.7中看到这一点,则会发现其他问题。你能给我看一个能说明你所看到的问题的要点或点心吗?我当然不能在python 2.7中重现任何与setattr()有关的问题。
- 我只是在自己的基础上提出任何例外。但我调用的库代码可能会引发其他问题。这是在python 2.6上的——我没有选择在运行它的机器上使用更新的东西(CentOS 2.6,香草安装中没有2.7)。我将尝试想出一段代码来演示它,是的。给我点时间。
- @0xC0000022L:2.6时不会传递字符串异常,不,能否打印出__exit__前两个参数的repr()?
- 结果2.6给了我一个字符串异常。我看到的字符串异常是"'Download' object has no attribute 'purge'",由于我不是将异常类型转换为字符串的人,所以我必须假设这是由python传递给我的。当然,现在我可以去解决问题了。但这是出乎意料的。
- @0xc0000022l:__exit__方法被赋予3个参数:类型、值和回溯。这种情况下是什么类型的?
- 它是str。CentOS是否有可能像Linux内核那样对python执行类似的操作?也就是说,根据需要进行混合和匹配,这样版本号就不会说真话了?python -V报告Python 2.6.6。
- @0xC00000022L:很奇怪,因为在2.6中尝试引发字符串异常会引发类型错误。根据引发异常的不同样式,查看2.6中的上下文管理器会发生什么。
- @我有一个假设不成立。也许CentOS在2.6版本中重新启用了字符串异常,而不是修复引发字符串异常的代码?当你把"foobar"输入python时,"raise"做什么?
- CentOS上的python 2.6给了我您描述的TypeError。
- 那就奇怪了;在调试器中,当上下文管理器传递字符串异常时,您知道它是实际引发的异常还是手动调用__exit__的其他异常吗?这个值看起来像一条AttributeError消息。
- 我将尝试创建一个模拟整体结构的最小示例。希望我能这样重现。是的,它看起来像一个AttributeError。至少,我的代码中没有明确地调用__exit__()。
- @0xc0000022l:看起来这是一个python 2.6问题,请参见`uuuexituuu()`(context manager)中的'excu value'参数是字符串而不是异常(python 2.6)
- @0xc0000022l:发现了错误;这是Python2.6实现的问题:bugs.python.org/issue7853