浏览 3891 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2012-05-05
上文
接contextlib.contextmanager的用法是怎样的?我摘抄一下模块源代码 引用 Typical usage:
@contextmanager def some_generator(<arguments>): <setup> try: yield <value> finally: <cleanup> This makes this: with some_generator(<arguments>) as <variable>: <body> equivalent to this: <setup> try: <variable> = <value> <body> finally: <cleanup> 大致就是可以把程序中的主体,<body>从中抽取出来,放到with块之中。而之后处理的<finally>都可以放到some_generator函数里面。 some_generator函数的编写很好办。只是contextmanager这个装饰器应该作为一个函数好呢还是一个类好呢?这是个很有意思的问题,我也想了好一会,等下一起讨论下。先给出我的解法: class MyGeneratorContextManager(object): def __init__(self, gen): print("__init__ called") self.gen = gen def __enter__(self): print("__enter__ called") return self.gen.next() def __exit__(self, exc_type, exc_val, exc_tb): print("__exit__called exc_type = %s, exc_val = %s, exc_tb = %s"\ % (exc_type, exc_val, exc_tb)) # 这里没有做异常处理,需要处理StopIteration异常 # 不是用return也可以 # 下面这句话将输出yield [1, 2, 3]后面的的打印语句end foo return self.gen.next() def MyContextManager(func): def tmpf(*args): print("func info:", func) return MyGeneratorContextManager(func(*args)) return tmpf @MyContextManager def foo(val): # 尝试用老方法捕捉错误 try: print("start foo", val) yield [1, 2, 3] # 下面一行需要调用self.gen.next()才能输出 print("end foo") except (Exception, AssertionError): # 但是实际上并没有捕捉到yield中的错误 # except的功能完全被__exit__取代 print("EXCEPTION ENCOUNTERED!") finally: print("FINALLY") print("foo is ", foo) print("foo() is ", foo("bbbb")) print("\nWITH INFO BELOW:") with foo("aaaa") as tmp: print("START WITH") #: tmp实际上就是yield穿过来的值 print(tmp) for i in tmp: print(i) assert 1>2 # 出错之后直接从with中跳出去,下面不可能被执行 print("END WITH") 输出结果是: # 首先可以看到foo的值是闭包中的tmpf函数 ('foo is ', <function tmpf at 0x7fb78b15f140>) ('func info:', <function foo at 0x7fb78b15f0c8>) __init__ called ('foo() is ', <__main__.MyGeneratorContextManager object at 0x7fb78b1591d0>) WITH INFO BELOW: # 请看,两次调用foo(),发现他们最终都是同一个foo函数 ('func info:', <function foo at 0x7fb78b15f0c8>) # 但是奇怪的是,函数被初始化了两次?这是因为这是个工厂模式,每次调用的函数虽然一样,但是会生成不同的类 __init__ called __enter__ called ('start foo', 'aaaa') START WITH [1, 2, 3] 1 2 3 # assert触发的错误没有被except捕捉到!被__exit__函数捕捉到了 __exit__called exc_type = <type 'exceptions.AssertionError'>, exc_val = , exc_tb = <traceback object at 0x7fb78b15c2d8> end foo FINALLY # 为什么跳出StopIteration异常?这是因为gen.next()已经走到头了,我们没有处理这异常 Traceback (most recent call last): File "/home/leonardo/Aptana Studio 3 Workspace/PythonStudy/src/thinking/mycontext_manager.py", line 68, in <module> print("END WITH") File "/home/leonardo/Aptana Studio 3 Workspace/PythonStudy/src/thinking/mycontext_manager.py", line 31, in __exit__ return self.gen.next() StopIteration 首先创建了一个工厂模式的MyContextManager,它实际上又是个闭包函数,也可以作为装饰器方便以后使用。 其次我定义了一个类MyGeneratorContextManager,这个函数在初始化的时候,就接收一个generator。请注意,接收的是generator而不是function。 请看函数 def tmpf(*args): print("func info:", func) return MyGeneratorContextManager(func(*args)) func在这里虽然是一个函数,但是func(*args)是一个generator,忘记传参数就糟了 在__enter__中最紧要的是要 return self.gen.next() 因为我们在with中面对的是一个generator,如果不对其进行next(),这个函数是不会动的。 进入到with块之后,foo函数里yield的值会直接传给tmp,这个值无关紧要。with块中所有语句就好像全部被填到yield那个地方去了一样。 程序于是执行with块中的语句,当其中出现异常的时候,我们外围的except语句块应该迅速捕捉到这一点,并输出才对。实际上不是,实际上当foo函数出现异常的时候,__exit__函数是第一时间捕捉到这个异常的。通过它的打印信息我们可以看出。 当处理完这个异常之后,我们调用了一下self.gen.next(),这个语句保证field语句后面的语句会被执行。最后执行了finally中的内容。你看except语句块被完全架空了。 好,我们回过头来再看,我们到底实现了什么东西?我们想执行的是 引用 <setup>
try: <variable> = <value> <body> finally: <cleanup> 现在我们把body单独抽出来了,放到with当中,把variable value替换成了yield value。这样做到掐头去尾,把前后不变的东西拿了出来,把内部的东西抽了出来。 咦?这不就是个装饰器么?错。装饰器是保持真身不懂,把前后改了,这是两个不同的方面。 最后我们回到我一开始提出的问题,为什么要设计一个mycontextmanager函数?可不可以不用工厂模式,直接用MyGeneratorContextManager函数一步到位? 这有两个前提条件要注意。第一,如果用class作为装饰器,这个类必须是可调用的(callable),如果将来我们要使用它,只可能它的调用__call__函数,因为它又要实现能在call之后调用__exit__,所以它只能返回self, type(self)之类的东西。第二,因为foo()是用在with语句中的,所以它必须是一个generator,也就是说foo.__call__()的返回结果是一个generator。那么可以找到有这么一个返回值,它既是self, type(self)这样类相关的类型,而且还是一个包含yield的generator么?:-) 要是对yield还不熟悉的朋友可能现在还不是很清楚,尤其是generator函数和with块中代码的关系,很可能还有一些把这代码再折腾折腾的想法。嗯,这里我就不写了,有问题给我留言吧! 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |