Python 的 with 语句详解
一、简介 with是从Python 2.5 引入的一个新的语法,更准确的说,是一种上下文的管理协议,用于简化try…except…finally的处理流程。with通过__enter__方法初始化,然后在__exit__中做善后以及处理异常。对于一些需要预先设置,事后要清理的一些任务,with提供了一种非常方便的表达。 with的基本语法如下,EXPR是一个任意表达式,VAR是一个单一的变量(可以是tuple),”as VAR”是可选的。 复制代码 代码如下: with EXPR as VAR: BLOCK 根据PEP 343的解释,with…as…会被翻译成以下语句: 复制代码 代码如下: 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) 为什么这么复杂呢?注意finally中的代码,需要BLOCK被执行后才会执行finally的清理工作,因为当EXPR执行时抛出异常,访问mgr.exit执行就会报AttributeError的错误。
根据前面对with的翻译可以看到,被with求值的对象必须有一个__enter__方法和一个__exit__方法。稍微看一个文件读取的例子吧,注意在这里我们要解决2个问题:文件读取异常,读取完毕后关闭文件句柄。用try…except一般会这样写: 复制代码 代码如下: f = open('/tmp/tmp.txt') try: for line in f.readlines(): print(line) finally: f.close() 注意我们这里没有处理文件打开失败的IOError,上面的写法可以正常工作,但是对于每个打开的文件,我们都要手动关闭文件句柄。如果要使用with来实现上述功能,需要需要一个代理类: 复制代码 代码如下: class opened(object): def __init__(self,name): def __enter__(self): def __exit__(self,type,value,trackback): with opened('/tmp/a.txt') as f: 注意我们定了一个名字叫opened的辅助类,并实现了__enter__和__exit__方法,__enter__方法没有参数,__exit__方法的3个参数,分别代表异常的类型、值、以及堆栈信息,如果没有异常,3个入参的值都为None。 如果你不喜欢定义class,还可以用Python标准库提供的contextlib来实现: 复制代码 代码如下: from contextlib import contextmanager @contextmanager with opened('/tmp/a.txt') as f: 使用contextmanager的函数,yield只能返回一个参数,而yield后面是处理清理工作的代码。在我们读取文件的例子中,就是关闭文件句柄。这里原理上和我们之前实现的类opened是相同的,有兴趣的可以参考一下contextmanager的源代码。 三、应用场景 一个确保代码执行前加锁,执行后释放锁的模板: 复制代码 代码如下: @contextmanager def locked(lock): lock.acquire() try: yield finally: lock.release() with locked(myLock): 数据库事务的提交和回滚: 复制代码 代码如下: @contextmanager def transaction(db): db.begin() try: yield None except: db.rollback() raise else: db.commit() 重定向stdout: 复制代码 代码如下: @contextmanager def stdout_redirected(new_stdout): save_stdout = sys.stdout sys.stdout = new_stdout try: yield None finally: sys.stdout = save_stdout with opened(filename,"w") as f: 注意上面的例子不是线程安全的,再多线程环境中要小心使用。
with是对try…expect…finally语法的一种简化,并且提供了对于异常非常好的处理方式。在Python有2种方式来实现with语法:class-based和decorator-based,2种方式在原理上是等价的,可以根据具体场景自己选择。 with最初起源于一种block…as…的语法,但是这种语法被很多人所唾弃,最后诞生了with,关于这段历史依然可以去参考PEP-343和PEP-340 您可能感兴趣的文章:
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |