Python中的"with"语句的用途是什么?
今天我第一次接触到Python的with
语句。我已经轻松使用Python几个月了,居然不知道它的存在!考虑到它有点冷门,我觉得问问大家也不错:
- Python的
with
语句是用来干什么的? - 你通常用它来做什么?
- 使用它时有没有需要注意的地方,或者常见的误用情况?有没有什么情况下用
try..finally
比用with
更好? - 为什么它没有被更广泛地使用?
- 哪些标准库的类可以和它一起使用?
11 个回答
Python中的with
语句是语言内置的一种功能,灵感来源于C++中的一种常用方法,叫做资源获取即初始化
。这个功能的目的是为了安全地获取和释放操作系统的资源。
with
语句可以在一个特定的范围内创建资源。你可以在这个范围内使用这些资源。当你离开这个范围时,这些资源会被干净利落地释放,不管你在这个范围内的代码是正常结束还是因为出现了错误而结束。
Python库中有很多资源都遵循with
语句所要求的规则,因此可以直接使用。不过,任何人都可以通过实现一个文档清晰的协议,来创建可以在with
语句中使用的资源,具体可以参考PEP 0343。
当你在应用程序中获取一些必须明确释放的资源,比如文件、网络连接、锁等时,建议使用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
语句有关。
我觉得这个问题之前已经有其他用户回答过了,所以我只是补充一下:
with
语句通过把一些常见的准备和清理工作封装在所谓的上下文管理器中,简化了异常处理。更多的细节可以在PEP 343中找到。例如,open
语句本身就是一个上下文管理器,它可以让你打开一个文件,只要你在with
语句的上下文中,它就会保持打开状态,而一旦你离开这个上下文,它就会自动关闭,不管你是因为异常还是正常流程离开的。因此,with
语句的用法和C++中的RAII模式类似:在with
语句中获取某些资源,并在你离开with
上下文时释放它。一些例子包括:使用
with open(filename) as fp:
打开文件,使用with lock:
获取锁(这里的lock
是threading.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.stdin
、sys.stdout
和sys.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