Python中如何使用with语句实现上下文管理器,与from contextlib import closing有何关联?
- 内容介绍
- 文章标签
- 相关推荐
本文共计2864个文字,预计阅读时间需要12分钟。
Python中`with`用法及原理(上下文管理器)
前言:`with`语句适用于对资源进行访问的场景,确保在操作结束后资源被正确释放。下面将简单介绍`with`语句的用法和原理。
`with`语句适用于对资源进行访问的场景,如文件、网络连接等。使用`with`语句可以确保在操作结束后资源被正确释放,避免资源泄漏。
示例:
pythonwith open('example.txt', 'r') as f: content=f.read()
上述代码中,`with`语句确保在读取文件内容后,文件被自动关闭,释放资源。
原理:
`with`语句内部使用了上下文管理器(context manager)的概念。上下文管理器是一种在执行代码块之前和之后自动执行特定代码的对象。
当使用`with`语句时,Python会自动调用对象的`__enter__`和`__exit__`方法。
- `__enter__`方法:在代码块开始执行前调用,返回一个对象,通常用于获取资源。- `__exit__`方法:在代码块执行完成后调用,负责清理资源,如关闭文件、断开网络连接等。
通过实现这两个方法,对象可以成为一个有效的上下文管理器。
例如,文件对象就是一个上下文管理器:
pythonclass File: def __enter__(self): print(文件打开) return self
def __exit__(self, exc_type, exc_value, traceback): print(文件关闭)
with File() as f: pass
运行上述代码,输出:
文件打开文件关闭
总结:
`with`语句在Python中用于资源管理,通过上下文管理器确保资源在操作结束后被正确释放。掌握`with`语句的用法和原理,有助于提高代码的可读性和健壮性。
python中 with 用法及原理(上下文管理器)
前言
with 语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源,比如文件使用后自动关闭/线程中锁的自动获取和释放等。
问题引出
如下代码:
file = open("1.txt")data = file.read()
file.close()
上面代码存在2个问题:
①文件读取发生异常,但没有进行任何处理;
②可能忘记关闭文件句柄;
改进
try:f = open('xxx')
except:
print('fail to open')
exit(-1)
try:
do something
except:
do something
finally:
f.close()
虽然这段代码运行良好,但比较冗长。
而使用with语句的话,能够减少冗长,还能自动处理上下文环境产生的异常。如下面代码:
with open("1.txt") as file:data = file.read()
with 工作原理
①紧跟with后面的语句被求值后,返回对象的__enter__方法被调用,返回值将被赋值给as后面的变量;
②当with语句体全部被执行完之后,将调用前面返回对象的__exit__方法。
with工作原理代码示例:
class Sample:def __enter__(self):
print("in __enter__")
return "Foo"
def __exit__(self, exc_type, exc_val, exc_tb):
print("in __exit__")
def get_sample():
return Sample()
with get_sample() as sample:
print("Sample: ", sample)
运行结果:
整个运行过程如下:
(1)__enter__方法被执行;
(2)__enter__方法的返回值,在这个例子中是“Foo”,赋值给变量sample;
(3)执行代码块,打印sample变量的值为“Foo”;
(4)__exit__方法被调用;
__exit__方法中有3个参数, exc_type, exc_val,exc_tb,这些参数在异常处理中相当有用。
参数解释:
exc_type:错误的类型
exc_val:错误类型对应的值
exc_tb:代码中错误发生的位置
示例代码:
class Sample:def __enter__(self):
print('in enter')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("type: ", exc_type)
print("val: ", exc_val)
print("tb: ", exc_tb)
def do_something(self):
bar = 1 / 0
return bar + 10
with Sample() as sample:
sample.do_something()
运行结果:
总结
实际上,在with后面的代码块抛出异常时,__exit__方法被执行。开发库时,清理资源,关闭文件等操作,都可以放在__exit__方法中。
总之,with-as表达式极大的简化了每次写finally的工作,这对代码的优雅性是有极大帮助的。
只要实现了__enter__() 和 __exit__()这两个方法的类都可以轻松创建上下文管理器,就能使用with。
如果有多项,可以这样写:
With open('1.txt') as f1, open('2.txt') as f2:do something
with语句的原理
- 上下文管理协议(Context Management Protocol):包含方法 __enter__()和__exit__(),支持该协议的对象要实现这两个方法。
- 上下文管理器(Context Manager):支持上下文管理协议的对象,这种对象实现了__enter__()和__exit__()方法。上下文管理器定义执行with语句时要建立的运行时上下文,负责执行with语句块上下文中的进入与退出操作。通常使用with语句调用上下文管理器,也可以通过直接调用其方法来使用。
with语句的常用表达式:
with EXPR as VAR: # EXPR可以是任意表达式BLOCK
其一般的执行过程是这样的:
1、执行EXPR,生成上下文管理器context_manager;
2、获取上下文管理器的__exit()__方法,并保存起来用于之后的调用;
3、调用上下文管理器的__enter__()方法;如果使用了as子句,则将__enter__()方法的返回值赋值给as子句中的VAR;
4、执行BLOCK中的表达式;
5、不管是否执行过程中是否发生了异常,执行上下文管理器的__exit__()方法,__exit__()方法负责执行“清理”工作,如释放资源等。
如果执行过程中没有出现异常,或者with语句体中执行了语句break/continue/return,则以None作为参数调用__exit__(None, None, None);如果执行过程中出现异常,则使用sys.exc_info得到的异常信息为参数调用__exit__(exc_type, exc_value, exc_traceback);
6、出现异常时,如果__exit__(type, value, traceback)返回False,则会重新抛出异常,让with之外的语句逻辑来处理异常,这也是通用做法;如果返回True,则忽略异常,不再对异常进行处理。
自定义上下文管理器
python的with语句是提供一个有效的机制,让代码更简练,同时在异常产生时,清理工作更简单。
示例1:
class DBManager(object):def __init__(self):
pass
def __enter__(self):
print('__enter__')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print('__exit__')
return True
def getInstance():
return DBManager()
with getInstance() as dbManagerIns:
print('with demo')
with后面必须跟一个上下文管理器,如果使用了as,则是把上下文管理器的 __enter__方法的返回值赋值给 target,target 可以是单个变量,或者由“()”括起来的元组
运行结果:
结果分析:当我们使用with语句的时候,__enter__方法被调用,并且将__enter__方法返回值赋值给as后面的变量,并且在退出with的时候自动执行__exit__方法。
示例2:
class With_work(object):def __enter__(self):
"""进入with语句的时候被调用"""
print('①enter called')
return "②打印对象f的值"
def __exit__(self, exc_type, exc_val, exc_tb):
"""离开with的时候被with调用"""
print('④exit called')
with With_work() as f:
print(f)
print('③打印with代码块中的输出')
print('⑤with代码块执行完毕之后的打印')
运行结果:
没有实现__enter__() 和 __exit__()这两个方法的类都不能创建上下文管理器,不能使用with语句。
例如:
class Door(object):def open(self):
print('Door is opened')
def close(self):
print('Door is closed')
with Door() as d:
d.open()
运行结果:
python中 from contextlib import closing 的使用
官方:docs.python.org/dev/library/contextlib.html
1、python中有些类没有实现__enter__() 和 __exit__()这两个方法也是可以使用with语句。但是前提是实现了close()语句。
例如:
import contextlibclass Door(object):
def open(self):
print('Door is opened')
def close(self):
print('Door is closed')
with contextlib.closing(Door()) as door:
door.open()
运行结果:
2、contextlib.closing(xxx)原理如下:
class closing(object):"""Context to automatically close something at the end of a block.
Code like this:
with closing(<module>.open(<arguments>)) as f:
<block>
is equivalent to this:
f = <module>.open(<arguments>)
try:
<block>
finally:
f.close()
"""
def __init__(self, thing):
self.thing = thing
def __enter__(self):
return self.thing
def __exit__(self, *exc_info):
self.thing.close()
contextlib.closing()会自动帮某些类加上__enter__()和__exit__()这两个方法,使其满足上下文管理器的条件。
3、并不是只有类才能满足上下文管理器的条件,其实方法也可以实现一个上下文管理器
可以通过@contextlib.contextmanager装饰器的方式实现,但是其装饰的方法必须是一个生成器。yield关键字前半段用来表示__enter__()方法,yield关键字后半段用来表示__exit__() 方法。
例如:
import contextlib@contextlib.contextmanager
def tag(name):
print("<%s>" % name)
yield
print("</%s>" % name)
with tag(name="h1"):
print('hello world!')
运行结果:
4、使用contextlib.contextmanager实现装饰器才能做的事情
例如:比如给一段代码加时间花费计算。
普通装饰器版本:
import timedef wrapper(func):
def new_func(*args, **kwargs):
t1 = time.time()
ret = func(*args, **kwargs)
t2 = time.time()
print('cost time=', (t2 - t1))
return ret
return new_func
@wrapper
def hello(a, b):
time.sleep(1)
print('a + b = ', a + b)
if __name__ == '__main__':
hello(100, 200)
运行结果:
contextlib.contextmanger版本:
import timeimport contextlib
@contextlib.contextmanager
def cost_time():
t1 = time.time()
yield
t2 = time.time()
print('cost time=', t2 - t1)
with cost_time():
time.sleep(1)
a = 100
b = 200
print('a + b = ', a + b)
原理:
1、因为cost_time()方法是个生成器,所以运行__enter__()的时候,contextmanager调用self.gen.next()会跑到cost_time()方法的yield处,停住挂起,这个时候已经有了t1=time.time();
2、然后运行with语句体里面的语句,也就是a+b=300;
3、跑完后运行__exit__()的时候,contextmanager调用 self.gen.next()会从cost_time()的yield的下一句开始一直到结束。这个时候有了t2=time.time(),t2-t1从而实现了统计cost_time的效果。
5、contextlib.contextmanager源码如下:
import sysclass GeneratorContextManager(object):
"""Helper for @contextmanager decorator."""
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:
if value is None:
# Need to force instantiation so we can reliably
# tell if we get the same exception back
value = type()
try:
self.gen.throw(type, value, traceback)
raise RuntimeError("generator didn't stop after throw()")
except StopIteration as exc:
return exc is not value
except:
if sys.exc_info()[1] is not value:
raise
def contextmanager(func):
@wraps(func)
def helper(*args, **kwds):
return GeneratorContextManager(func(*args, **kwds))
return helper
去期待陌生,去拥抱惊喜。
本文共计2864个文字,预计阅读时间需要12分钟。
Python中`with`用法及原理(上下文管理器)
前言:`with`语句适用于对资源进行访问的场景,确保在操作结束后资源被正确释放。下面将简单介绍`with`语句的用法和原理。
`with`语句适用于对资源进行访问的场景,如文件、网络连接等。使用`with`语句可以确保在操作结束后资源被正确释放,避免资源泄漏。
示例:
pythonwith open('example.txt', 'r') as f: content=f.read()
上述代码中,`with`语句确保在读取文件内容后,文件被自动关闭,释放资源。
原理:
`with`语句内部使用了上下文管理器(context manager)的概念。上下文管理器是一种在执行代码块之前和之后自动执行特定代码的对象。
当使用`with`语句时,Python会自动调用对象的`__enter__`和`__exit__`方法。
- `__enter__`方法:在代码块开始执行前调用,返回一个对象,通常用于获取资源。- `__exit__`方法:在代码块执行完成后调用,负责清理资源,如关闭文件、断开网络连接等。
通过实现这两个方法,对象可以成为一个有效的上下文管理器。
例如,文件对象就是一个上下文管理器:
pythonclass File: def __enter__(self): print(文件打开) return self
def __exit__(self, exc_type, exc_value, traceback): print(文件关闭)
with File() as f: pass
运行上述代码,输出:
文件打开文件关闭
总结:
`with`语句在Python中用于资源管理,通过上下文管理器确保资源在操作结束后被正确释放。掌握`with`语句的用法和原理,有助于提高代码的可读性和健壮性。
python中 with 用法及原理(上下文管理器)
前言
with 语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源,比如文件使用后自动关闭/线程中锁的自动获取和释放等。
问题引出
如下代码:
file = open("1.txt")data = file.read()
file.close()
上面代码存在2个问题:
①文件读取发生异常,但没有进行任何处理;
②可能忘记关闭文件句柄;
改进
try:f = open('xxx')
except:
print('fail to open')
exit(-1)
try:
do something
except:
do something
finally:
f.close()
虽然这段代码运行良好,但比较冗长。
而使用with语句的话,能够减少冗长,还能自动处理上下文环境产生的异常。如下面代码:
with open("1.txt") as file:data = file.read()
with 工作原理
①紧跟with后面的语句被求值后,返回对象的__enter__方法被调用,返回值将被赋值给as后面的变量;
②当with语句体全部被执行完之后,将调用前面返回对象的__exit__方法。
with工作原理代码示例:
class Sample:def __enter__(self):
print("in __enter__")
return "Foo"
def __exit__(self, exc_type, exc_val, exc_tb):
print("in __exit__")
def get_sample():
return Sample()
with get_sample() as sample:
print("Sample: ", sample)
运行结果:
整个运行过程如下:
(1)__enter__方法被执行;
(2)__enter__方法的返回值,在这个例子中是“Foo”,赋值给变量sample;
(3)执行代码块,打印sample变量的值为“Foo”;
(4)__exit__方法被调用;
__exit__方法中有3个参数, exc_type, exc_val,exc_tb,这些参数在异常处理中相当有用。
参数解释:
exc_type:错误的类型
exc_val:错误类型对应的值
exc_tb:代码中错误发生的位置
示例代码:
class Sample:def __enter__(self):
print('in enter')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("type: ", exc_type)
print("val: ", exc_val)
print("tb: ", exc_tb)
def do_something(self):
bar = 1 / 0
return bar + 10
with Sample() as sample:
sample.do_something()
运行结果:
总结
实际上,在with后面的代码块抛出异常时,__exit__方法被执行。开发库时,清理资源,关闭文件等操作,都可以放在__exit__方法中。
总之,with-as表达式极大的简化了每次写finally的工作,这对代码的优雅性是有极大帮助的。
只要实现了__enter__() 和 __exit__()这两个方法的类都可以轻松创建上下文管理器,就能使用with。
如果有多项,可以这样写:
With open('1.txt') as f1, open('2.txt') as f2:do something
with语句的原理
- 上下文管理协议(Context Management Protocol):包含方法 __enter__()和__exit__(),支持该协议的对象要实现这两个方法。
- 上下文管理器(Context Manager):支持上下文管理协议的对象,这种对象实现了__enter__()和__exit__()方法。上下文管理器定义执行with语句时要建立的运行时上下文,负责执行with语句块上下文中的进入与退出操作。通常使用with语句调用上下文管理器,也可以通过直接调用其方法来使用。
with语句的常用表达式:
with EXPR as VAR: # EXPR可以是任意表达式BLOCK
其一般的执行过程是这样的:
1、执行EXPR,生成上下文管理器context_manager;
2、获取上下文管理器的__exit()__方法,并保存起来用于之后的调用;
3、调用上下文管理器的__enter__()方法;如果使用了as子句,则将__enter__()方法的返回值赋值给as子句中的VAR;
4、执行BLOCK中的表达式;
5、不管是否执行过程中是否发生了异常,执行上下文管理器的__exit__()方法,__exit__()方法负责执行“清理”工作,如释放资源等。
如果执行过程中没有出现异常,或者with语句体中执行了语句break/continue/return,则以None作为参数调用__exit__(None, None, None);如果执行过程中出现异常,则使用sys.exc_info得到的异常信息为参数调用__exit__(exc_type, exc_value, exc_traceback);
6、出现异常时,如果__exit__(type, value, traceback)返回False,则会重新抛出异常,让with之外的语句逻辑来处理异常,这也是通用做法;如果返回True,则忽略异常,不再对异常进行处理。
自定义上下文管理器
python的with语句是提供一个有效的机制,让代码更简练,同时在异常产生时,清理工作更简单。
示例1:
class DBManager(object):def __init__(self):
pass
def __enter__(self):
print('__enter__')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print('__exit__')
return True
def getInstance():
return DBManager()
with getInstance() as dbManagerIns:
print('with demo')
with后面必须跟一个上下文管理器,如果使用了as,则是把上下文管理器的 __enter__方法的返回值赋值给 target,target 可以是单个变量,或者由“()”括起来的元组
运行结果:
结果分析:当我们使用with语句的时候,__enter__方法被调用,并且将__enter__方法返回值赋值给as后面的变量,并且在退出with的时候自动执行__exit__方法。
示例2:
class With_work(object):def __enter__(self):
"""进入with语句的时候被调用"""
print('①enter called')
return "②打印对象f的值"
def __exit__(self, exc_type, exc_val, exc_tb):
"""离开with的时候被with调用"""
print('④exit called')
with With_work() as f:
print(f)
print('③打印with代码块中的输出')
print('⑤with代码块执行完毕之后的打印')
运行结果:
没有实现__enter__() 和 __exit__()这两个方法的类都不能创建上下文管理器,不能使用with语句。
例如:
class Door(object):def open(self):
print('Door is opened')
def close(self):
print('Door is closed')
with Door() as d:
d.open()
运行结果:
python中 from contextlib import closing 的使用
官方:docs.python.org/dev/library/contextlib.html
1、python中有些类没有实现__enter__() 和 __exit__()这两个方法也是可以使用with语句。但是前提是实现了close()语句。
例如:
import contextlibclass Door(object):
def open(self):
print('Door is opened')
def close(self):
print('Door is closed')
with contextlib.closing(Door()) as door:
door.open()
运行结果:
2、contextlib.closing(xxx)原理如下:
class closing(object):"""Context to automatically close something at the end of a block.
Code like this:
with closing(<module>.open(<arguments>)) as f:
<block>
is equivalent to this:
f = <module>.open(<arguments>)
try:
<block>
finally:
f.close()
"""
def __init__(self, thing):
self.thing = thing
def __enter__(self):
return self.thing
def __exit__(self, *exc_info):
self.thing.close()
contextlib.closing()会自动帮某些类加上__enter__()和__exit__()这两个方法,使其满足上下文管理器的条件。
3、并不是只有类才能满足上下文管理器的条件,其实方法也可以实现一个上下文管理器
可以通过@contextlib.contextmanager装饰器的方式实现,但是其装饰的方法必须是一个生成器。yield关键字前半段用来表示__enter__()方法,yield关键字后半段用来表示__exit__() 方法。
例如:
import contextlib@contextlib.contextmanager
def tag(name):
print("<%s>" % name)
yield
print("</%s>" % name)
with tag(name="h1"):
print('hello world!')
运行结果:
4、使用contextlib.contextmanager实现装饰器才能做的事情
例如:比如给一段代码加时间花费计算。
普通装饰器版本:
import timedef wrapper(func):
def new_func(*args, **kwargs):
t1 = time.time()
ret = func(*args, **kwargs)
t2 = time.time()
print('cost time=', (t2 - t1))
return ret
return new_func
@wrapper
def hello(a, b):
time.sleep(1)
print('a + b = ', a + b)
if __name__ == '__main__':
hello(100, 200)
运行结果:
contextlib.contextmanger版本:
import timeimport contextlib
@contextlib.contextmanager
def cost_time():
t1 = time.time()
yield
t2 = time.time()
print('cost time=', t2 - t1)
with cost_time():
time.sleep(1)
a = 100
b = 200
print('a + b = ', a + b)
原理:
1、因为cost_time()方法是个生成器,所以运行__enter__()的时候,contextmanager调用self.gen.next()会跑到cost_time()方法的yield处,停住挂起,这个时候已经有了t1=time.time();
2、然后运行with语句体里面的语句,也就是a+b=300;
3、跑完后运行__exit__()的时候,contextmanager调用 self.gen.next()会从cost_time()的yield的下一句开始一直到结束。这个时候有了t2=time.time(),t2-t1从而实现了统计cost_time的效果。
5、contextlib.contextmanager源码如下:
import sysclass GeneratorContextManager(object):
"""Helper for @contextmanager decorator."""
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:
if value is None:
# Need to force instantiation so we can reliably
# tell if we get the same exception back
value = type()
try:
self.gen.throw(type, value, traceback)
raise RuntimeError("generator didn't stop after throw()")
except StopIteration as exc:
return exc is not value
except:
if sys.exc_info()[1] is not value:
raise
def contextmanager(func):
@wraps(func)
def helper(*args, **kwds):
return GeneratorContextManager(func(*args, **kwds))
return helper
去期待陌生,去拥抱惊喜。

