Python中的"with"语句的用途是什么?

514 投票
11 回答
126595 浏览
提问于 2025-04-15 23:47

今天我第一次接触到Python的with语句。我已经轻松使用Python几个月了,居然不知道它的存在!考虑到它有点冷门,我觉得问问大家也不错:

  1. Python的with语句是用来干什么的?
  2. 你通常用它来做什么?
  3. 使用它时有没有需要注意的地方,或者常见的误用情况?有没有什么情况下用try..finally比用with更好?
  4. 为什么它没有被更广泛地使用?
  5. 哪些标准库的类可以和它一起使用?

11 个回答

46

Python中的with语句是语言内置的一种功能,灵感来源于C++中的一种常用方法,叫做资源获取即初始化。这个功能的目的是为了安全地获取和释放操作系统的资源。

with语句可以在一个特定的范围内创建资源。你可以在这个范围内使用这些资源。当你离开这个范围时,这些资源会被干净利落地释放,不管你在这个范围内的代码是正常结束还是因为出现了错误而结束。

Python库中有很多资源都遵循with语句所要求的规则,因此可以直接使用。不过,任何人都可以通过实现一个文档清晰的协议,来创建可以在with语句中使用的资源,具体可以参考PEP 0343

当你在应用程序中获取一些必须明确释放的资源,比如文件、网络连接、锁等时,建议使用with语句。

103

我想推荐两个有趣的讲座:

  • PEP 343 关于“with”语句
  • Effbot 理解Python的“with”语句

1. with语句用于将一段代码块的执行包裹起来,这个包裹是由一个叫做上下文管理器的东西定义的。这样可以把常见的try...except...finally使用模式封装起来,方便以后重复使用。

2. 你可以这样做:

with open("foo.txt") as foo_file:
    data = foo_file.read()

或者

from contextlib import nested
with nested(A(), B(), C()) as (X, Y, Z):
   do_something()

或者(Python 3.1)

with open('data') as input_file, open('result', 'w') as output_file:
   for line in input_file:
     output_file.write(parse(line))

或者

lock = threading.Lock()
with lock:
    # Critical section of code

3. 我觉得这里没有什么反模式。
引用一下Dive into Python上的话:

try..finally很好,with更好。

4. 我想这和程序员习惯使用其他语言中的try..catch..finally语句有关。

453
  1. 我觉得这个问题之前已经有其他用户回答过了,所以我只是补充一下:with 语句通过把一些常见的准备和清理工作封装在所谓的上下文管理器中,简化了异常处理。更多的细节可以在PEP 343中找到。例如,open 语句本身就是一个上下文管理器,它可以让你打开一个文件,只要你在with语句的上下文中,它就会保持打开状态,而一旦你离开这个上下文,它就会自动关闭,不管你是因为异常还是正常流程离开的。因此,with 语句的用法和C++中的RAII模式类似:在with语句中获取某些资源,并在你离开with上下文时释放它。

  2. 一些例子包括:使用with open(filename) as fp:打开文件,使用with lock:获取锁(这里的lockthreading.Lock的一个实例)。你还可以使用contextlib中的contextmanager装饰器来构建自己的上下文管理器。例如,当我需要暂时更改当前目录,然后再返回原来的目录时,我经常会使用这个:

    from contextlib import contextmanager
    import os
    
    @contextmanager
    def working_directory(path):
        current_dir = os.getcwd()
        os.chdir(path)
        try:
            yield
        finally:
            os.chdir(current_dir)
    
    with working_directory("data/stuff"):
        # do something within data/stuff
    # here I am back again in the original working directory
    

    这里还有一个例子,它暂时将sys.stdinsys.stdoutsys.stderr重定向到其他文件句柄,并在之后恢复它们:

    from contextlib import contextmanager
    import sys
    
    @contextmanager
    def redirected(**kwds):
        stream_names = ["stdin", "stdout", "stderr"]
        old_streams = {}
        try:
            for sname in stream_names:
                stream = kwds.get(sname, None)
                if stream is not None and stream != getattr(sys, sname):
                    old_streams[sname] = getattr(sys, sname)
                    setattr(sys, sname, stream)
            yield
        finally:
            for sname, stream in old_streams.iteritems():
                setattr(sys, sname, stream)
    
    with redirected(stdout=open("/tmp/log.txt", "w")):
         # these print statements will go to /tmp/log.txt
         print "Test entry 1"
         print "Test entry 2"
    # back to the normal stdout
    print "Back to normal stdout again"
    

    最后,还有一个例子,它创建一个临时文件夹,并在离开上下文时清理它:

    from tempfile import mkdtemp
    from shutil import rmtree
    
    @contextmanager
    def temporary_dir(*args, **kwds):
        name = mkdtemp(*args, **kwds)
        try:
            yield name
        finally:
            shutil.rmtree(name)
    
    with temporary_dir() as dirname:
        # do whatever you want
    

撰写回答