理解Python中的with语句和上下文管理器

31 投票
6 回答
15750 浏览
提问于 2025-04-16 04:01

我正在尝试理解 with 语句。我知道它是用来替代 try/except 代码块的。

现在假设我做了这样的事情:

try:
   name = "rubicon" / 2  # to raise an exception
except Exception as e:
   print("No, not possible.")
finally:
   print("OK, I caught you.")

我该如何用上下文管理器来替换这个呢?

6 个回答

16

在Python中,with的作用是用来包裹一组语句,这些语句通常涉及到资源的设置和释放。它有点像try...finally,因为即使发生了错误,finally部分的代码也会被执行。

上下文管理器是一种对象,它实现了两个方法:__enter____exit__。这两个方法分别在with代码块开始之前和结束之后被调用。

比如,我们来看一个经典的open()例子:

with open('temp.txt', 'w') as f:
    f.write("Hi!")

open函数返回一个File对象,这个对象的__enter__方法大致就像return self,而__exit__方法则像self.close(),用于关闭文件。

33

contextlib.contextmanager 是一个函数装饰器,它可以让你轻松地创建一个上下文管理器,而不需要自己写一个完整的 ContextManager 类(这个类里需要有 __enter____exit__ 方法,你还得记住 __exit__ 方法的参数,或者记得 __exit__ 方法必须 return True 来抑制异常)。相反,你只需要写一个函数,在你希望 with 块运行的地方放一个 yield,然后像平常一样处理任何异常(这些异常实际上是从 yield 产生的)。

from contextlib import contextmanager
@contextmanager
def handler():
    # Put here what would ordinarily go in the `__enter__` method
    # In this case, there's nothing to do
    try:
        yield # You can return something if you want, that gets picked up in the 'as'
    except Exception as e:
        print "no not possible"
    finally:
        print "Ok I caught you"

with handler():
    name='rubicon'/2 #to raise an exception

为什么还要费力去写一个上下文管理器呢?因为代码可以重用。你可以在多个地方使用同一个上下文管理器,而不需要重复处理异常。如果异常处理是特定于某个情况的,那就没必要用上下文管理器。但如果同样的模式经常出现(或者可能会出现在你的用户那里,比如关闭文件、解锁互斥锁),那么花点时间去写一个上下文管理器是值得的。如果异常处理比较复杂,这种方式也很不错,因为它把异常处理和主要的代码流程分开了。

32

with 其实并不是完全替代 try/except,而是更像是替代 try/finally。不过,你可以让上下文管理器在出现异常和没有异常的情况下做不同的事情:

class Mgr(object):
    def __enter__(self): pass
    def __exit__(self, ext, exv, trb):
        if ext is not None: print "no not possible"
        print "OK I caught you"
        return True

with Mgr():
    name='rubicon'/2 #to raise an exception

这里的 return True 是上下文管理器决定抑制异常的地方(就像你在 except 语句中不重新抛出异常一样)。

撰写回答