我在这个问题上挣扎了一段时间。在我正在编写的一些代码中,我需要编写一堆文件,如果需要,还可以选择创建目录树。我的想法如下:捕捉异常IOError,如果它的第一个参数是enent,那么创建目录结构并尝试再次写入该文件。在
我已经编写了一个相对较小的重试函数,但我想将其概括为可能引发异常的“任何”代码。直到我遇到这样的事情:
def retry(f):
def wrapper(*args, **kwargs):
try:
return f(*args, **kwargs)
except:
print "Gotcha here!"
return wrapper
def update(file, value):
@contextmanager
@retry
def safeopen(file, mode):
with open(file, mode) as f:
yield f
try:
with safeopen(file, 'w') as f:
f.write(value)
except:
print "Gotcha there!"
update( 'tests/nonexisting/dummy.txt', 'Dummy line')
我已经将代码压缩到最小值,以显示当open()
抛出异常时失败的内容。在这段代码中,异常只从update()
中的except块捕获,而不是在wrapper()
中捕获,所以我总是得到Gotcha there!
,尽管我希望是{f()
捕获异常。在
我做错什么了?在
问题是您将
@contextmanager
修饰符与普通函数混合在一起。@retry
修饰符是一个普通函数,但是你用它来装饰一个@contextmanager
生成器-这并不是你期望的那样,因为当你调用一个@contextmanager
函数时,它的函数体并不是实际执行的。相反,返回一个GeneratorContextManager
对象。除非直接调用GeneratorContextManager
的__enter__
方法,否则函数体不会执行,无论是直接调用还是使用with
语句。在考虑这个例子:
It输出:
^{pr2}$如您所见,我们在进入
safeopen
的主体之前退出了retry
包装器,因为safeopen
是一个上下文管理器。直到GeneratorContextManager
对象被实际返回并作为with
语句的一部分进行求值,主体才被执行,但此时它已经太晚了;retry
已经退出。在要解决此问题,您还需要使
retry
a@contextmanager
,并使用它装饰safeopen
上下文管理器:输出:
编辑:
如果颠倒decorator的顺序,使
retry
直接装饰safeopen
,则可以使retry
实现更简单,因为现在要装饰的是生成器函数,而不是上下文管理器:相关问题 更多 >
编程相关推荐