Python contextlib.contextmanager
看着代码又发现了一个奇怪的东西:
@contextlib.contextmanager def __call__(self, incoming): result_wrapper = [] yield lambda: result_wrapper.append( self._dispatch_and_handle_error(incoming)) if result_wrapper[0] == NotificationResult.HANDLED: incoming.acknowledge() else: incoming.requeue()
contextlib.contextmanager(func)
This function is a decorator that can be used to define a factory function for with statement context managers, without needing to create a class or separate __enter__() and __exit__() methods.
A simple example (this is not recommended as a real way of generating HTML!):
from contextlib import contextmanager
@contextmanager
def tag(name):
print "<%s>" % name
yield
print "</%s>" % name
>>> with tag("h1"):
... print "foo"
...
<h1>
foo
</h1>
The function being decorated must return a generator-iterator when called. This iterator must yield exactly one value, which will be bound to the targets in the with statement’s as clause, if any.
At the point where the generator yields, the block nested in the with statement is executed. The generator is then resumed after the block is exited. If an unhandled exception occurs in the block, it is reraised inside the generator at the point where the yield occurred. Thus, you can use a try...except...finally statement to trap the error (if any), or ensure that some cleanup takes place. If an exception is trapped merely in order to log it or to perform some action (rather than to suppress it entirely), the generator must reraise that exception. Otherwise the generator context manager will indicate to the with statement that the exception has been handled, and execution will resume with the statement immediately following the with statement.
也就是说用了contextmanager装饰器的generator,yield语句之前的部分会在进入with的语句块之前执行(generator生成过程,generator函数开始到yield之间的部分),yield的值可以用于with中的as来定义个语句块内的变量。yield的值相当于是__enter__函数的返回,而yield语句后面部分将会在with语句块退出时执行,相当在__exit__函数中执行。
先来试验一下派森大法中generator的执行步骤:
>>> def FakeGenerator(): ... print 'iteration begin' ... yield {"msg":'Hello'} ... print 'iteration end' ... >>> for obj in FakeGenerator(): ... print 'get msg:', obj['msg'] ... iteration begin get msg: Hello iteration end
也就是说每次迭代中FakeGenerator只会执行到yield语句,然后就获取yield返回的对象给for循环里的语句使用,等到下次迭代时再从yield后面的语句开始执行,真是吊炸天!这样就几乎实现了with语句中对象加了__enter__和__exit__方法时的功能。
不过每次要写个for来代替with显得太土,于是就有了我们的@contextmanager装饰器,它的实现或许是这样的
>>> class FakeGeneratorContext(object): ... def __init__(self, func): ... self.generator_func = func ... self.__exit__ = None ... self.__enter__= None ... self.generator = None ... def __enter__(self): ... self.generator = self.generator_func() ... return self.generator.next() ... def __exit__(self, ext, exv, tb): ... try: ... self.generator.next() ... except StopIteration: ... pass >>> def fakecontext(func): ... def wrapper(): ... context = FakeGeneratorContext(func) ... return context ... return wrapper ... >>> @fakecontext ... def FakeGenerator(): ... print 'iteration begin' ... yield {'msg':'Hello'} ... print 'iteration end' ... >>> with FakeGenerator() as obj: ... print obj["msg"] ... iteration begin Hello iteration end
有点烦了,再玩下去就和搞Java的一样了