StackContext allows applications to maintain threadlocal-like state that follows execution as it moves to other execution contexts.python
an exception handler is a kind of stack-local state and when that stack is suspended and resumed in a new context that state needs to be preserved.閉包
一個棧結構的上下文處理類
異常處理也是一個棧結構的上下文應用app
@contextlib.contextmanager
def die_on_error():
try:
yield
except:
logging.error("exception in asynchronous operation",exc_info=True)
sys.exit(1)
with StackContext(die_on_error):
# Any exception thrown here *or in callback and its desendents*
# will cause the process to exit instead of spinning endlessly
# in the ioloop.
http_client.fetch(url, callback)
ioloop.start()
複製代碼
from __future__ import with_statement
import contextlib
import functools
import itertools
import logging
import threading
複製代碼
class _State(threading.local):
def __init__(self):
self.contexts = ()
_state = _State()
複製代碼
全局上下文保存整個執行程序的上下文(棧)
with StackContext(context) 使程序包裹在 (global_context, context)上執行
執行完成後恢復全局上下文less
class StackContext(object):
def __init__(self, context_factory):
self.context_factory = context_factory
def __enter__(self):
self.old_contexts = _state.contexts
_state.contexts = (self.old_contexts +
((StackContext, self.context_factory),))
try:
self.context = self.context_factory()
self.context.__enter__()
except Exception:
_state.contexts = self.old_contexts
raise
def __exit__(self, type, value, traceback):
try:
return self.context.__exit__(type, value, traceback)
finally:
_state.contexts = self.old_contexts
複製代碼
捕獲上下文執行中拋出而又未被捕獲的異常
做用相似 finally
用於執行在程序拋出異常後記錄日誌、關閉 socket 這些現場清理工做
若是 exception_handler 中返回 True,代表異常已經被處理,不會再拋出socket
from tornado import ioloop
import tornado.stack_context
import contextlib
ioloop = tornado.ioloop.IOLoop.instance()
@contextlib.contextmanager
def context_without_catch():
print("enter context")
yield
print("exit context")
def exception_handler(type, value, traceback):
print "catch uncaught exception:", type, value, traceback
return True
def main():
with tornado.stack_context.ExceptionStackContext(exception_handler):
with tornado.stack_context.StackContext(context_without_catch):
print 0 / 0
main()
# enter context
# catch uncaught exception: <type 'exceptions.ZeroDivisionError'> integer division or modulo by zero <traceback object at 0x0000000003321FC8>
複製代碼
__exit__ 中捕獲 with 語句所包裹的程序執行中所拋出的異常,調用註冊的 exception_handler 進行處理
exception_handler 返回 True,則異常不會蔓延async
class ExceptionStackContext(object):
def __init__(self, exception_handler):
self.exception_handler = exception_handler
def __enter__(self):
self.old_contexts = _state.contexts
_state.contexts = (self.old_contexts +
((ExceptionStackContext, self.exception_handler),))
def __exit__(self, type, value, traceback):
try:
if type is not None:
return self.exception_handler(type, value, traceback)
finally:
_state.contexts = self.old_contexts
複製代碼
臨時構造一個空的全局上下文函數
class NullContext(object):
def __enter__(self):
self.old_contexts = _state.contexts
_state.contexts = ()
def __exit__(self, type, value, traceback):
_state.contexts = self.old_contexts
複製代碼
之因此進行這樣複雜的操做,是爲了對某些前面執行環境相同的狀況省略前面的構造,節省時間,不然,能夠用一行代替:tornado
new_contexts = ([NullContext()] + [cls(arg) for (cls,arg) in contexts])
oop
def wrap(fn):
if fn is None:
return None
def wrapped(callback, contexts, *args, **kwargs):
# 函數實際調用時,上下文環境發生了變化,與`contexts = _state.contexts`已經有所不一樣
if (len(_state.contexts) > len(contexts) or
any(a[1] is not b[1]
for a, b in itertools.izip(_state.contexts, contexts))):
# contexts have been removed or changed, so start over
new_contexts = ([NullContext()] +
[cls(arg) for (cls,arg) in contexts])
else:
new_contexts = [cls(arg)
for (cls, arg) in contexts[len(_state.contexts):]]
if len(new_contexts) > 1:
with contextlib.nested(*new_contexts):
callback(*args, **kwargs)
elif new_contexts:
with new_contexts[0]:
callback(*args, **kwargs)
else:
callback(*args, **kwargs)
if getattr(fn, 'stack_context_wrapped', False):
return fn
# 保存上下文環境
contexts = _state.contexts
result = functools.partial(wrapped, fn, contexts)
result.stack_context_wrapped = True
return result
複製代碼
author:bigfish
copyright: 許可協議 知識共享署名-非商業性使用 4.0 國際許可協議fetch