java中的接口是类吗
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(
contextlib.closing() 会自动帮某些类加上 __enter__() 和 __exit__() 这两个方法,使其满足上下文管理器的条件。
3、并不是只有类才能满足上下文管理器的条件,其实方法也可以实现一个上下文管理器【contextlib.contextmanager】
可以通过 @contextlib.contextmanager 装饰器的方式实现,但是其装饰的方法必须是一个生成器。 yield 关键字前半段用来表示 __enter__() 方法, yield 关键字后半段用来表示 __exit__() 方法。
例如:
import contextlib@contextlib.contextmanagerdef 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@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小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~