场景
1.在很多项目代码里, 经常会遇到语句with
的使用, 这个with
到底如何使用的呢, 它又会用在哪方面呢?
2.with
语句是python
里特殊的语句, 在Java,Object-C,C++
里是没有的, 它是否是关键的语句, 没有这个语句是不是很多场景就没有替代方案了?
说明
0.with
语句并不是必须的语句, 它的替代方案就是try/finally
语句. 只不过with
语句需要的对象比较特殊, 需要自带__enter
和__exit__
实现,和C++的构造和析构函数很像, 在对象生命周期开始和结束时自动分别调用构造和析构函数. 它只是一个具备执行流程顺序的封装语句而已, 只是在资源创建销毁时使用, 起到精简代码的作用.
1.语法
with_stmt ::= "with" with_item ("," with_item)* ":" suite
with_item ::= expression ["as" target]
或者
with EXPR as VAR:
BLOCK
python解析器在内部大概会解析为:
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)
with
内部执行顺序说明:
- 1.在
with_item
里的上下文表达式执行获取一个context manager
. - 2.加载
context manager
力的__exit__()
, 注意是加载, 不是调用. - 3.调用
context manager
里的__enter__
函数. - 4.如果
target
或者说有as
关键字, 那么__enter__
方法的返回值赋值给target
. - 5.执行
suite
- 6.
context manager
的__exit__
被调用, 如果有异常, 那么traceback
会传递给__exit__
, 否则传递None
2.with
语句目的就是为了提供一个标准的try/finally
使用方式. 注意, 没有except
, 该抛出的异常还是会抛出的. 它的重点在context manager
上, 满足contextmanager protocol
(协议)的对象称之为context manager
. 这个协议对象必须实现了 __enter__()
和 __exit__()
方法.在标准库里, files
, sockets
, 和locks
对象都实现了这个协议.
比如
- file
- thread.LockType
- threading.Lock
- threading.RLock
- threading.Condition
- threading.Semaphore
- threading.BoundedSemaphore
3.open()
的位置在 The Python Standard Library
->Built in Functions
里. 如果打开失败, 那么会抛出一个 OSError
错误. with
是捕抓不到这个异常的.
4.自定义contextmanager
的方式有两种, 一种是定义一个类, 并实现__enter__
和__exit__
方法; 另外一种是使用@contextmanager
来修饰generator
函数, 这种比较方便, 只需要定义一个函数即可.
例子
以下例子列举了contextmanager
的两种创建方式:
import io
from contextlib import contextmanager
class tranaction:
def __init__(self, obj):
print ("get a db handle")
self.obj = obj
def __enter__(self):
print ("begin db tranaction")
return self.obj
def __exit__(self, *exc_info):
if exc_info is None:
print ("rollback db tranaction")
else:
print ("commit db tranaction")
@contextmanager
def TranactionDecorator(obj):
print("begin db tranaction")
try:
yield None
except Exception:
print("do db rollback")
else:
print("commit db tranaction")
if __name__ == '__main__':
print ("====== tranaction class ======")
with tranaction(None):
print ("do insert intot sql")
print("====== Tranaction Generator1 ======")
with TranactionDecorator(None):
print("do insert intot sql-1")
raise 1
print("====== Tranaction Generator2 ======")
with TranactionDecorator(None):
print("do insert intot sql-2")
输出
====== tranaction class ======
get a db handle
begin db tranaction
do insert intot sql
commit db tranaction
====== Tranaction Generator1 ======
begin db tranaction
do insert intot sql-1
do db rollback
====== Tranaction Generator2 ======
begin db tranaction
do insert intot sql-2
commit db tranaction
参考
-
Python document 里的 8.5. The with statement.
file object
An object exposing a file-oriented API (with methods such as read() or write()) to an underlying
resource. Depending on the way it was created, a file object can mediate access to a real
on-disk file or to another type of storage or communication device (for example standard
input/output, in-memory buffers, sockets, pipes, etc.). File objects are also called file-like
objects or streams.
There are actually three categories of file objects: raw binary files, buffered binary files
and text files. Their interfaces are defined in the io module. The canonical way to
create a file object is by using the open() function
generator
A function which returns a generator iterator. It looks like a normal function except that it contains yield expressions for producing a series of values usable in a for-loop or that can be retrieved one at a time with the next() function.
Usually refers to a generator function, but may refer to a generator iterator in some contexts. In cases where the intended meaning isn’t clear, using the full terms avoids ambiguity.
generator iterator
An object created by a generator function.
Each yield temporarily suspends processing, remembering the location execution state (including local variables and pending try-statements). When the generator iterator resumes, it picks up where it left off (in contrast to functions which start fresh on every invocation).
常见的contextmanager
修饰符实现方式:
class GeneratorContextManager(object):
def __init__(self, gen):
self.gen = gen
def __enter__(self):
try:
return self.gen.next()
except StopIteration:
raise RuntimeError("generator didn't yield")
def __exit__(self, type, value, traceback):
if type is None:
try:
self.gen.next()
except StopIteration:
return
else:
raise RuntimeError("generator didn't stop")
else:
try:
self.gen.throw(type, value, traceback)
raise RuntimeError("generator didn't stop after throw()")
except StopIteration:
return True
except:
# only re-raise if it's *not* the exception that was
# passed to throw(), because __exit__() must not raise
# an exception unless __exit__() itself failed. But
# throw() has to raise the exception to signal
# propagation, so this fixes the impedance mismatch
# between the throw() protocol and the __exit__()
# protocol.
#
if sys.exc_info()[1] is not value:
raise
def contextmanager(func):
def helper(*args, **kwds):
return GeneratorContextManager(func(*args, **kwds))
return helper