Python中的with语句

ref:

https://docs.python.org/release/2.6/whatsnew/2.6.html#pep-343-the-with-statement

https://www.ibm.com/developerworks/cn/opensource/os-cn-pythonwith/

https://www.python.org/dev/peps/pep-0343/

摘自文档:

with替代了之前在python里使用try...finally来做清理工作的方法。基本形式如下:

with expression [as variable]:
    with-block

当expression执行的时候,返回一个支持context management protocol(有__enter__(), __exit__()方法)的对象

这个对象的__enter__()方法在with-block执行前运行,该方法返回的结果赋给variable(如果variable存在的话)

with-block执行之后,__exit__()方法被调用,在这里可以执行清理工作

If BLOCK raises an exception, the __exit__(type, value, traceback)() is called with the exception details

with语句的运行过程如下:

mgr = (EXPR)
exit = type(mgr).__exit__  # Not calling it yet
value = type(mgr).__enter__(mgr)
exc = True
try:
    try:
        VAR = value  # Only if "as VAR" is present
        BLOCK
    except:
        # The exceptional case is handled here
        exc = False
        if not exit(mgr, *sys.exc_info()):
            raise
        # The exception is swallowed if exit() returns true
finally:
    # The normal and non-local-goto cases are handled here
    if exc:
        exit(mgr, None, None, None)

也就是说:

如果执行过程中没有出现异常,或者语句体中执行了语句 break/continue/return,

则以 None 作为参数调用 __exit__(None, None, None) ;

如果执行过程中出现异常,则使用 sys.exc_info 得到的异常信息为参数调用 __exit__(exc_type, exc_value, exc_traceback)

出现异常时,如果 __exit__(type, value, traceback) 返回 False,则会重新抛出异常,让with 之外的语句逻辑来处理异常,这也是通用做法;如果返回 True,则忽略异常,不再对异常进行处理

支持context management protocol的python对象有file object, threading locks variables, localcontext() fucntion in decimal module

在文档里,有一个连接数据库的例子(省略了部分非关键代码):

class DatabaseConnection:
    # Database interface
    def cursor(self):
        "Returns a cursor object and starts a new transaction"
    def commit(self):
        "Commits current transaction"
    def rollback(self):
        "Rolls back current transaction"
    def __enter__(self):
        # Code to start a new transaction
        cursor = self.cursor()
        return cursor
    def __exit__(self, type, value, tb):
        if tb is None:
            # No exception, so commit
            self.commit()
        else:
            # Exception occurred, so rollback.
            self.rollback()
            # return False

然后就可以这样使用了:

db_connection = DatabaseConnection()
with db_connection as cursor:
    cursor.execute('insert into ...')
    cursor.execute('delete from ...')
    # ... more operations ...