python中 with 用法及原理(上下文管理器) || python中 from contextlib import closing 的使用(python中[:3])

网友投稿 288 2022-08-28


python中 with 用法及原理(上下文管理器) || python中 from contextlib import closing 的使用(python中[:3])

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 somethingexcept: do somethingfinally: 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 + 10with 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 Truedef 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 的使用

官方:​​和  __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(.open()) as f: is equivalent to this: f = .open() try: 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】

可以通过 @contextlib.contextmanager 装饰器的方式实现,但是其装饰的方法必须是一个生成器。 yield 关键字前半段用来表示 __enter__() 方法, yield 关键字后半段用来表示 __exit__()  方法。

例如:

import contextlib@contextlib.contextmanagerdef tag(name): print("<%s>" % name) yield print("" % 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@wrapperdef hello(a, b): time.sleep(1) print('a + b = ', a + b)if __name__ == '__main__': hello(100, 200)

运行结果:

contextlib.contextmanger版本:

import timeimport contextlib@contextlib.contextmanagerdef 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: raisedef contextmanager(func): @wraps(func) def helper(*args, **kwds): return GeneratorContextManager(func(*args, **kwds)) return helper

去期待陌生,去拥抱惊喜。


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:java数据结构与算法之马踏棋盘
下一篇:python中 with 用法及原理(上下文管理器) || python中 from contextlib import closing 的使用(python中input的用法)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~