本文和大家分享的主要是python的with语句和上下文管理器相关内容,一起来看看吧,希望对大家
学习python
有所帮助。
With语句的语法和原理
基本语法
with是用来操作上下文管理器的。
上下文管理器(Context Manager):支持上下文管理协议的对象,这种对象实现了 __enter__() 和 __exit__() 方法。上下文管理器定义执行 with 语句时要建立的运行时上下文,负责执行 with 语句块上下文中的进入与退出操作。通常使用 with 语句调用上下文管理器,也可以通过直接调用其方法来使用。
换句话说,实现了 __enter__() 和 __exit__() 的就是上下文管理器,就可以使用 with 语句。
with context_expression [as target(s)]:
with-body
使用 with 语句,会先执行上下文管理器context_expression的 __enter__() 方法,如果该方法有返回值,将返回值赋值给as的变量,然后执行 with 语句块的内容,最后执行 with-body 。
上下文管理器的基本语法:
@contextlib.contextmanager
def some_generator(<arguments>):
<setup>
try:
yield <value>
finally:
<cleanup>
with some_generator(<arguments>) as <variable>:
<body>
将一个上下文管理器用 try-catch 的方式实现:
<setup>
try:
<variable> = <value>
<body>
finally:
<cleanup>
原理
with的执行过程如下:
context_manager = context_expression
exit = type(context_manager).__exit__
value = type(context_manager).__enter__(context_manager)
exc = True # True 表示正常执行,即便有异常也忽略;False 表示重新抛出异常,需要对异常进行处理
try:
try:
target = value # 如果使用了 as 子句
with-body # 执行 with-body
except:
# 执行过程中有异常发生
exc = False
# 如果 __exit__ 返回 True,则异常被忽略;如果返回 False,则重新抛出异常
# 由外层代码对异常进行处理
if not exit(context_manager, *sys.exc_info()):
raise
finally:
# 正常退出,或者通过 statement-body 中的 break/continue/return 语句退出
# 或者忽略异常退出
if exc:
exit(context_manager, None, None, None)
# 缺省返回 None,None 在布尔上下文中看做是 False
首先,执行 __enter__() ,返回值赋值给 value 。
然后执行 with 的 with-body 代码块,如果上一步有 value ,则使用。
如果 with-body 执行过程中发生异常,执行 __exit__() 方法。没有发生异常也会执行 __exit__() 方法,区别就是exc变量。
如果发生异常退出的, __exit__() 如果返回 True ,则异常被忽略;否则异常被重新抛出,交给with外面的代码处理。如果 __exit__() 发生了异常,则会取代 with-body 中的异常抛出。
要处理异常时,不要显示重新抛出异常,即不能重新抛出通过参数传递进来的异常,只需要将返回值设置为 False 就可以了。之后,上下文管理代码会检测是否 __exit__() 失败来处理异常。
自定义上下文管理器
定义一个实现了 __enter__() 和 __exit__() 的对象即可。
• context_manager.__enter__() :进入上下文管理器的运行时上下文,在语句体执行前调用。with 语句将该方法的返回值赋值给 as 子句中的 target,如果指定了 as 子句的话
• context_manager.__exit__(exc_type, exc_value, exc_traceback) :退出与上下文管理器相关的运行时上下文,返回一个布尔值表示是否对 with-body 过程中发生的异常进行处理。参数表示引起退出操作的异常,如果退出时没有发生异常,则3个参数都为 None 。如果发生异常,返回True 表示不处理 with-body 的异常,否则会在退出该方法后重新抛出异常以由 with 语句之外的代码逻辑进行处理。如果该方法内部产生异常,则会取代由 statement-body 中语句产生的异常。 要处理异常时,不要显示重新抛出异常,即不能重新抛出通过参数传递进来的异常,只需要将返回值设置为 False 就可以了。之后,上下文管理代码会检测是否 __exit__() 失败来处理异常。
contextlib模块
这个模块提供了很多使用的装饰器和函数。
装饰器 contextmanager
装饰一个生成器函数,函数被装饰过后, 返回一个上下文管理器对象。
from contextlib import contextmanager
@contextmanager
def demo():
print '[Allocate resources]'
print 'Code before yield-statement executes in __enter__'
yield '*** contextmanager demo ***'
print 'Code after yield-statement executes in __exit__'
print '[Free resources]'
with demo() as value:
# 这里,demo()返回了一个上下文管理器对象
# 相当于 context_obj = demo(); with context_obj as value:...
print 'Assigned Value: %s' % value
yield 之前的语句,作为 __enter__() 执行,之后的语句作为 __exit__() 执行。 yield 作为 __enter__() 的返回值。
函数 nested
nested 可以将多个上下文管理器组织在一起,避免使用嵌套 with 语句。
with nested(A(), B(), C()) as (X, Y, Z):
# with-body code here
类似于:
with A() as X:
with B() as Y:
with C() as Z:
# with-body code here
上下文管理器 closing
closing 会将包装的对象返回给 as ,并在with代码块结束的时候调用对象的 close() 方法:
class ClosingDemo(object):
def __init__(self):
self.acquire()
def acquire(self):
print 'Acquire resources.'
def free(self):
print 'Clean up any resources acquired.'
def close(self):
self.free()
with closing(ClosingDemo()):
print 'Using resources'
一个小问题
之前有点疑惑,为什么python内置的 open() 既没有被 @contextmanager 装饰器装饰,又不是一个对象,为什么可以使用with语句呢?
还在 staskoverflow 提问了这个问题。
原来, open() 返回的是一个继承自 IObase 的对象,这个对象也是一个上下文管理器,所以就支持 with 语句了。
来源:网络
With语句的语法和原理
基本语法
with是用来操作上下文管理器的。
上下文管理器(Context Manager):支持上下文管理协议的对象,这种对象实现了 __enter__() 和 __exit__() 方法。上下文管理器定义执行 with 语句时要建立的运行时上下文,负责执行 with 语句块上下文中的进入与退出操作。通常使用 with 语句调用上下文管理器,也可以通过直接调用其方法来使用。
换句话说,实现了 __enter__() 和 __exit__() 的就是上下文管理器,就可以使用 with 语句。
with context_expression [as target(s)]:
with-body
使用 with 语句,会先执行上下文管理器context_expression的 __enter__() 方法,如果该方法有返回值,将返回值赋值给as的变量,然后执行 with 语句块的内容,最后执行 with-body 。
上下文管理器的基本语法:
@contextlib.contextmanager
def some_generator(<arguments>):
<setup>
try:
yield <value>
finally:
<cleanup>
with some_generator(<arguments>) as <variable>:
<body>
将一个上下文管理器用 try-catch 的方式实现:
<setup>
try:
<variable> = <value>
<body>
finally:
<cleanup>
原理
with的执行过程如下:
context_manager = context_expression
exit = type(context_manager).__exit__
value = type(context_manager).__enter__(context_manager)
exc = True # True 表示正常执行,即便有异常也忽略;False 表示重新抛出异常,需要对异常进行处理
try:
try:
target = value # 如果使用了 as 子句
with-body # 执行 with-body
except:
# 执行过程中有异常发生
exc = False
# 如果 __exit__ 返回 True,则异常被忽略;如果返回 False,则重新抛出异常
# 由外层代码对异常进行处理
if not exit(context_manager, *sys.exc_info()):
raise
finally:
# 正常退出,或者通过 statement-body 中的 break/continue/return 语句退出
# 或者忽略异常退出
if exc:
exit(context_manager, None, None, None)
# 缺省返回 None,None 在布尔上下文中看做是 False
首先,执行 __enter__() ,返回值赋值给 value 。
然后执行 with 的 with-body 代码块,如果上一步有 value ,则使用。
如果 with-body 执行过程中发生异常,执行 __exit__() 方法。没有发生异常也会执行 __exit__() 方法,区别就是exc变量。
如果发生异常退出的, __exit__() 如果返回 True ,则异常被忽略;否则异常被重新抛出,交给with外面的代码处理。如果 __exit__() 发生了异常,则会取代 with-body 中的异常抛出。
要处理异常时,不要显示重新抛出异常,即不能重新抛出通过参数传递进来的异常,只需要将返回值设置为 False 就可以了。之后,上下文管理代码会检测是否 __exit__() 失败来处理异常。
自定义上下文管理器
定义一个实现了 __enter__() 和 __exit__() 的对象即可。
• context_manager.__enter__() :进入上下文管理器的运行时上下文,在语句体执行前调用。with 语句将该方法的返回值赋值给 as 子句中的 target,如果指定了 as 子句的话
• context_manager.__exit__(exc_type, exc_value, exc_traceback) :退出与上下文管理器相关的运行时上下文,返回一个布尔值表示是否对 with-body 过程中发生的异常进行处理。参数表示引起退出操作的异常,如果退出时没有发生异常,则3个参数都为 None 。如果发生异常,返回True 表示不处理 with-body 的异常,否则会在退出该方法后重新抛出异常以由 with 语句之外的代码逻辑进行处理。如果该方法内部产生异常,则会取代由 statement-body 中语句产生的异常。 要处理异常时,不要显示重新抛出异常,即不能重新抛出通过参数传递进来的异常,只需要将返回值设置为 False 就可以了。之后,上下文管理代码会检测是否 __exit__() 失败来处理异常。
contextlib模块
这个模块提供了很多使用的装饰器和函数。
装饰器 contextmanager
装饰一个生成器函数,函数被装饰过后, 返回一个上下文管理器对象。
from contextlib import contextmanager
@contextmanager
def demo():
print '[Allocate resources]'
print 'Code before yield-statement executes in __enter__'
yield '*** contextmanager demo ***'
print 'Code after yield-statement executes in __exit__'
print '[Free resources]'
with demo() as value:
# 这里,demo()返回了一个上下文管理器对象
# 相当于 context_obj = demo(); with context_obj as value:...
print 'Assigned Value: %s' % value
yield 之前的语句,作为 __enter__() 执行,之后的语句作为 __exit__() 执行。 yield 作为 __enter__() 的返回值。
函数 nested
nested 可以将多个上下文管理器组织在一起,避免使用嵌套 with 语句。
with nested(A(), B(), C()) as (X, Y, Z):
# with-body code here
类似于:
with A() as X:
with B() as Y:
with C() as Z:
# with-body code here
上下文管理器 closing
closing 会将包装的对象返回给 as ,并在with代码块结束的时候调用对象的 close() 方法:
class ClosingDemo(object):
def __init__(self):
self.acquire()
def acquire(self):
print 'Acquire resources.'
def free(self):
print 'Clean up any resources acquired.'
def close(self):
self.free()
with closing(ClosingDemo()):
print 'Using resources'
一个小问题
之前有点疑惑,为什么python内置的 open() 既没有被 @contextmanager 装饰器装饰,又不是一个对象,为什么可以使用with语句呢?
还在 staskoverflow 提问了这个问题。
原来, open() 返回的是一个继承自 IObase 的对象,这个对象也是一个上下文管理器,所以就支持 with 语句了。
来源:网络