gpt4 book ai didi

python - 将上下文管理器与装饰器混合使用时未捕获到异常

转载 作者:行者123 更新时间:2023-11-28 18:40:52 24 4
gpt4 key购买 nike

我一直在为这个问题苦苦挣扎。在我正在编写的一些代码中,我需要编写一堆文件并根据需要选择创建目录树。我的想法如下:捕获异常 IOError,如果它的第一个参数是 ENOENT,则创建目录结构并尝试再次写入文件。

我已经编写了一个相对较小的重试函数,但我想将其概括为可能引发异常的“任何”代码。这一切都有效,直到我遇到这样的事情:

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 block 中捕获,而不是在 wrapper() 中捕获,所以我总是 Gotcha there!尽管我期望 Gotcha here 而不是。我试过交换 @decorator 和 @contextmanager 行,没办法。我已经检查并确保包装器被调用:确实如此。只是它没有从 f() 中捕获异常。

我做错了什么?

最佳答案

问题是您将 @contextmanager 装饰器与普通函数混合在一起。 @retry 装饰器是一个普通函数,但您正在使用它来装饰一个 @contextmanager 生成器 - 这不会像您期望的那样运行,因为当你调用了一个 @contextmanager 函数,它的函数体实际上并没有被执行。相反,返回一个 GeneratorContextManager 对象。在 GeneratorContextManager__enter__ 方法被直接调用或使用 with 语句调用之前,函数体不会执行。

考虑这个例子:

from contextlib import contextmanager


def retry(f):
def wrapper(*args, **kwargs):
try:
print("in wrapper")
return f(*args, **kwargs)
except:
print "Gotcha here!"
finally:
print "done"
return wrapper

@contextmanager
@retry
def safeopen(file, mode):
print("in safe open")
with open(file, mode) as f:
yield f

def update(file, value):
try:
print("CALLING SAFE OPEN")
with safeopen(file, 'w') as f:
f.write(value)
except:
print "Gotcha there!"

update( 'tests/nonexisting/dummy.txt', 'Dummy line')

输出:

CALLING SAFE OPEN
in wrapper
done
in safe open
Gotcha there!

如您所见,我们在进入safeopen 主体之前就退出了retry 包装器,因为safeopen 是一个上下文管理器。直到 GeneratorContextManager 对象实际返回,并作为 with 语句的一部分进行评估,主体才被执行,但到那时为时已晚; retry 已退出。

要解决此问题,您还需要将 retry 设为 @contextmanager,并使用它来装饰 safeopen 上下文管理器:

from contextlib import contextmanager


def retry(f):
@contextmanager
def wrapper(*args, **kwargs):
try:
print("in wrapper")
with f(*args, **kwargs) as out:
yield out
except:
print "Gotcha here!"
finally:
print "done"
return wrapper

@retry
@contextmanager
def safeopen(file, mode):
print("in safe open")
with open(file, mode) as f:
yield f

def update(file, value):
print("CALLING SAFE OPEN")
with safeopen(file, 'w') as f:
f.write(value)

update( 'tests/nonexisting/dummy.txt', 'Dummy line')

输出:

CALLING SAFE OPEN
in wrapper
in safe open
Gotcha here!
done

编辑:

如果你颠倒装饰器的顺序,让retry直接装饰safeopen,你可以让retry的实现更简单一些,因为现在你正在装饰一个生成器函数,而不是一个上下文管理器:

def retry(f):
def wrapper(*args, **kwargs):
try:
print("in wrapper")
return next(f(*args, **kwargs)) # Call next on the generator object
except:
print "Gotcha here!"
finally:
print "done"
return wrapper

@contextmanager
@retry
def safeopen(file, mode):
print("in safe open")
with open(file, mode) as f:
yield f

关于python - 将上下文管理器与装饰器混合使用时未捕获到异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26217517/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com