未捕获将contextmanager与d混合的异常

2024-04-28 02:36:52 发布

您现在位置:Python中文网/ 问答频道 /正文

我在这个问题上挣扎了一段时间。在我正在编写的一些代码中,我需要编写一堆文件,如果需要,还可以选择创建目录树。我的想法如下:捕捉异常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!,尽管我希望是{}。我尝试过交换@decorator和@contextmanager行,不可能。我已经检查并确保包装器被调用:确实如此。只是它没有从f()捕获异常。在

我做错什么了?在


Tags: 文件代码目录returndefargsupdatewrapper
1条回答
网友
1楼 · 发布于 2024-04-28 02:36:52

问题是您将@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')

It输出:

^{pr2}$

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

要解决此问题,您还需要使retrya@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

编辑:

如果颠倒decorator的顺序,使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

相关问题 更多 >