我今天第一次遇到Python with语句。我已经简单地使用Python几个月了,甚至不知道它的存在!鉴于它的地位有些模糊,我认为有必要问一下:

Python with语句是什么 设计用于? 是什么 你用它干什么? 有吗? 我需要注意的问题,还是 相关联的常见反模式 它的使用?在什么情况下try. finally比with更好? 为什么它没有被更广泛地使用呢? 哪些标准库类与它兼容?


当前回答

I believe this has already been answered by other users before me, so I only add it for the sake of completeness: the with statement simplifies exception handling by encapsulating common preparation and cleanup tasks in so-called context managers. More details can be found in PEP 343. For instance, the open statement is a context manager in itself, which lets you open a file, keep it open as long as the execution is in the context of the with statement where you used it, and close it as soon as you leave the context, no matter whether you have left it because of an exception or during regular control flow. The with statement can thus be used in ways similar to the RAII pattern in C++: some resource is acquired by the with statement and released when you leave the with context. Some examples are: opening files using with open(filename) as fp:, acquiring locks using with lock: (where lock is an instance of threading.Lock). You can also construct your own context managers using the contextmanager decorator from contextlib. For instance, I often use this when I have to change the current directory temporarily and then return to where I was: 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 Here's another example that temporarily redirects sys.stdin, sys.stdout and sys.stderr to some other file handle and restores them later: 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" And finally, another example that creates a temporary folder and cleans it up when leaving the context: 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

其他回答

第1点、第2点和第3点得到了很好的阐述:

4:它相对较新,仅在python2.6+中可用(或python2.5使用from __future__ import with_statement)

另一个开箱即用支持的例子是流行数据库模块的连接对象,例如:

sqlite3 psycopg2 cx_oracle

连接对象是上下文管理器,因此可以在with-statement中开箱即用,但是在使用上述方法时请注意:

当with-block结束时,无论是否出现异常,连接都不会关闭。如果with-block以异常结束,则事务将回滚,否则事务将被提交。

这意味着程序员必须小心自己关闭连接,但允许获取一个连接,并在多个with-语句中使用它,如psycopg2文档所示:

conn = psycopg2.connect(DSN)

with conn:
    with conn.cursor() as curs:
        curs.execute(SQL1)

with conn:
    with conn.cursor() as curs:
        curs.execute(SQL2)

conn.close()

在上面的示例中,您将注意到psycopg2的游标对象也是上下文管理器。从行为的相关文档中:

当游标退出with-block时,游标将被关闭,释放与游标最终关联的所有资源。事务的状态不会受到影响。

反模式的一个例子可能是在循环内部使用with,而在循环外部使用with会更有效

例如

for row in lines:
    with open("outfile","a") as f:
        f.write(row)

vs

with open("outfile","a") as f:
    for row in lines:
        f.write(row)

第一种方法是为每一行打开和关闭文件,与第二种只打开和关闭一次文件的方法相比,这可能会导致性能问题。

I believe this has already been answered by other users before me, so I only add it for the sake of completeness: the with statement simplifies exception handling by encapsulating common preparation and cleanup tasks in so-called context managers. More details can be found in PEP 343. For instance, the open statement is a context manager in itself, which lets you open a file, keep it open as long as the execution is in the context of the with statement where you used it, and close it as soon as you leave the context, no matter whether you have left it because of an exception or during regular control flow. The with statement can thus be used in ways similar to the RAII pattern in C++: some resource is acquired by the with statement and released when you leave the with context. Some examples are: opening files using with open(filename) as fp:, acquiring locks using with lock: (where lock is an instance of threading.Lock). You can also construct your own context managers using the contextmanager decorator from contextlib. For instance, I often use this when I have to change the current directory temporarily and then return to where I was: 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 Here's another example that temporarily redirects sys.stdin, sys.stdout and sys.stderr to some other file handle and restores them later: 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" And finally, another example that creates a temporary folder and cleans it up when leaving the context: 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

with语句适用于所谓的上下文管理器:

http://docs.python.org/release/2.5.2/lib/typecontextmanager.html

这个想法是通过在离开with块后进行必要的清理来简化异常处理。一些python内置程序已经作为上下文管理器工作。