Tornado工具模块

Yeolar   2013-02-09 13:54  

Tornado源码剖析 Tornado异步网络访问

Tornado的工具模块包括一些方便的工具,比如自动重载、异步代码简化、命令行选项处理、测试等。和其他的同类库相比,主要区别是考虑了对异步的支持。

tornado.autoreload - 在开发模式下自动检测代码变更

关键字

functools.partial, sys.path, sys.modules, os.stat, signal.setitimer, subprocess.Popen, os.execv, os.spawnv, runpy.run_module, exec, globals, pkgutil.get_loader

自动重载模块可以自动检测代码的修改,并在有修改时,重启工程。它依赖于 ioloopIOLoop 和调度器 PeriodicCallback 。通过缓存文件的修改时间,并周期性地比较文件的修改时间,来检查文件是否被修改。如果文件发生变动,调用 _reload 重新启动。

该模块一般不会直接使用,而是把 debug=True 传递给 tornado.web.Application 的构造器。直接使用的话,可以执行:

python -m tornado.autoreload -m module.to.run [args...]
python -m tornado.autoreload path/to/script.py [args...]

它的入口函数为 wait ,但外面有一层 main ,用来在脚本执行时避免脚本的语法错误导致的无法执行到 wait

autoreload 中有一些复杂点的逻辑,一是要处理模块执行和脚本执行的区分,二是要处理Python在模块执行和脚本执行时 sys.path 的不同的结果。

tornado.gen - 简化异步代码

关键字

functools.wraps, with, assert, yield, __slots__

使用 gen 模块的好处是可以把一组用异步回调连在一起的函数写成一个单独的生成器。例子就不列举了,文档中写的很详细。生成器的实现利用了Python的 yield 关键字的 协程特性 ,有关它的原理可以见 2.5版yield之学习心得 。使用生成器时,首先把 @gen.engine 装饰器用在函数上,它会执行函数,对结果为 GeneratorType 类型的函数创建一个 Runner 类,并运行。 run 方法里面包含了大量的异常处理逻辑和代码,我们剥离这些代码,看看它实际干了什么:

 1 def run(self):
 2     if self.running or self.finished:
 3         return
 4     try:
 5         self.running = True
 6         while True:
 7             ...
 8             if not self.yield_point.is_ready():   # 等待下次激活run
 9                 return
10             next = self.yield_point.get_result()  # 取得任务函数执行结果,第一次为None
11             ...
12             try:
13                 ...
14                 yielded = self.gen.send(next)     # 把结果传递到断点
15             except StopIteration:                 # 结束
16                 self.finished = True
17                 ...
18                 return
19             ...
20             if isinstance(yielded, list):
21                 yielded = Multi(yielded)          # 把任务结成组
22             if isinstance(yielded, YieldPoint):
23                 self.yield_point = yielded
24                 self.yield_point.start(self)      # 把控制交给任务函数,重新执行
25                 ...
26             ...
27     finally:
28         self.running = False

异步回调会重新激活 rungen 本身并不带来异步,它只是简化了异步代码的处理,通过 yield 和保存每次中断时的结果来实现。

tornado.httputil - 操作HTTP头和URL

关键字

dict, urllib.urlencode, multipart/form-data, doctest.DocTestSuite

httputil 提供了几个HTTP头和URL操作的实用工具:

  • HTTPHeaders 是对HTTP头的对象化封装,它继承了 dict ,为每个键提供了字符串和列表两种读取方式,分别用 getget_list 方法,它是继承字典类的一个好例子。
  • url_concat 可以给URL增加参数。
  • parse_multipart_form_data 可以用来处理 multipart/form-data 类型的表单数据,但是它应该不支持多上传文件的表单,参见 表单内容类型 。它把参数传递的字典和文件列表进行更新,增加表单数据中的内容。

tornado.options - 解析命令行

关键字

sys._getframe, inspect (f_back, f_code, co_filename), xrange, logging, logging.Formatter, logging.LogRecord, textwrap.wrap, basestring, re, re.MatchObject, curses.setupterm, curses.tigetnum, curses.tigetstr, curses.tparm

options 模块实现了一个功能相对简单的参数选项解析器。 _Option 类对应每个选项,它可以对 datetimetimedeltaboolbasestring 类型做相应的检查和类型转换,同时还支持将多值的选项转换为列表。 _Options 类在 _Option 的基础上,继承 dict 来实现了一个解析器,支持从命令行和文件两种方式加载选项,同时对选项分组,默认按文件名分组。

options 模块定义了一个全局的 options 字典。

options 中包含了一些预定义的选项,主要为日志相关的配置。Tornado中有关日志的配置也放在了本模块中,并封装了 logging.Formatter 类,使其支持终端下的彩色输出,还做了一些格式调整。默认的配置会将日志输出到文件和终端错误输出。

除了对选项的数据结构的处理,本模块中有关日志格式化和终端彩色控制的方法也很值得一看。

tornado.process - 多进程工具

关键字

multiprocessing.cpu_count, os.sysconf, os.urandom, binascii.hexlify, random.seed, os.fork, os.wait

这是tornado自带的一个多进程模块,它可以根据指定的进程数 fork 子进程,并监视它们的状态。每个子进程关联到一个 task_id 。对于非正常结束的进程(信号或者非0退出),它会以相同的 task_id 重启它。 task_idrange(num_processes) 的某值。

如果没有指定进程数,会调用 cpu_count 计算CPU个数作为子进程数。在创建或重启子进程时,会执行 random.seed

需要注意它和 autoreload 模块不兼容(以及 debug=True )。使用时,必须在调用 fork_processes 之后创建 IOLoop

tornado.stack_context - 异步回调的异常处理

关键字

threading.local, operator.setitem, functools.partial, itertools.izip, contextlib.contextmanager, sys.exc_info

StackContext 让程序可以在执行到其他上下文时,保持像threadlocal一样的状态。比如用来避免显式地使用异步调用的封装器,以及保存一些用于logging的额外的上下文。

这个理解起来有点难度。异常处理器可以理解为一种本地栈的状态,栈在暂停和在新上下文中恢复时需要被保留。 StackContext 把恢复栈的工作转到一种把控制从一个上下文转移到另一个的机制上。这种机制的实现是通过一个本地栈来完成的。

关于如何使用,作者给了一些规则:

  • 如果在写一个不依赖于 stack_context 可知的库(如tornado的 ioloopiostream )的异步库,比如一个线程池,在任何异步操作之前使用 stack_context.wrap 来取得操作开始时的栈上下文的一个快照。
  • 如果在写一个有一些分享资源的异步库,如连接池,在 with stack_context.NullContext(): 块中创建那些分享资源。这可以防止 StackContexts 从一个请求泄漏到另一个。
  • 如果要写一些像会在异步调用期间存在的异常处理器之类的东西,创建一个 StackContext 或者 ExceptionStackContext ,并把异步调用放在它们的 with 块中。

我的理解是: StackContextExceptionStackContext 实现了上下文转移机制,可以用来创建对异步有效的上下文; NullContext 可以清空上下文,对那些不希望上下文互相污染的情况适用;而 wrap 用来给函数取一个上下文快照,这样函数在多线程或异步环境执行时,能够得到自己原来的上下文。

例:

 1 @contextlib.contextmanager
 2 def die_on_error():
 3     try:
 4         yield
 5     except Exception:
 6         logging.error('exception in asynchronous operation', exc_info=True)
 7         sys.exit(1)
 8 
 9 with StackContext(die_on_error):    # 代替了 with die_on_error():
10     # 对这里或回调中抛出的异常都有效
11     http_client.fetch(url, callback)
12 ioloop.start()

tornado.testing - 异步代码的单元测试

关键字

unittest.TestCase, unittest.main, contextlib.contextmanager, logging, StringIO, signal.signal

testing 模块提供了基于 unittest.TestCase 的异步代码单元测试。 AsyncTestCase 可以看做是对 IOLoop 的一个封装,并以 TestCase 的形式提供使用接口。除了 setUptearDown 等方法的重载之外,它主要通过 waitstop 两个方法实现对异步代码的支持, wait 封装了 IOLoop.start

 1 def wait(self, condition=None, timeout=5):
 2     if not self.__stopped:
 3         if timeout:
 4             def timeout_func():
 5                 ...
 6                 self.stop()
 7             ...
 8             self.__timeout = self.io_loop.add_timeout(time.time() + timeout, timeout_func)
 9         while True:
10             self.__running = True
11             with NullContext():
12                 self.io_loop.start()
13             ...
14     assert self.__stopped
15     self.__stopped = False
16     ...
17     result = self.__stop_args
18     self.__stop_args = None
19     return result

stop 则封装了 IOLoop.stop

1 def stop(self, _arg=None, **kwargs):
2     ...
3     self.__stop_args = kwargs or _arg
4     if self.__running:
5         self.io_loop.stop()
6         self.__running = False
7     self.__stopped = True

因为涉及到异步代码,所以 AsyncTestCase 中也包含了很多异常的处理。

另外 AsyncTestCase 默认对每个测试都新建一个 IOLoop 实例。

AsyncHTTPTestCase 继承 AsyncTestCase ,在它的基础上增加了 http_clienthttp_server 成员,可以用来测试如 Application 之类的HTTP服务。

testing 模块还提供了一个 LogTrapTestCase 类,它可以单独使用,也可以用来做多重继承,用途是关闭那些无关紧要的测试输出,它对当前的 logger 实例做了包装,对于只有一个 handler 且为 StreamHandler 实例的情况下,它使其只输出 errorsfailures

http://www.yeolar.com/note/2013/02/09/tornado-utilities/

http://www.yeolar.com/note/2013/02/09/tornado-utilities/