Tornado源码分析之起步

Tornado是什么

一个网络框架,同时也是一个异步网络库。其网络库适合长轮训和网络库。

源码下载

这里 将源码进行下载,下载后切换到分支1.2,因为最小的版本,则意味着源码分析的难度降低。我们看看如何切换。

切换分支到1.2

可以看到现在我么那种本地分支master分支上。通过git branch -a 可以查看所有的分支信息,下面是切换版本 使用 git checkout 来切换。

目录结构

其中,tornado目录为该文件的核心目录,我们将demos里面中的helloworld.py代码,拿出来,进行debug。

hello world.py

import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
from tornado.options import define, options
define("port", default=8888, help="run on the given port", type=int)
class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")
def main():
    tornado.options.parse_command_line()
    application = tornado.web.Application([
        (r"/", MainHandler),
    ])
    http_server = tornado.httpserver.HTTPServer(application)
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()
if __name__ == "__main__":
    main()

看上去很简单,就是当输入localhost:8888的时候,返回Hello, world。首先看一下,解析命令行参数。

parse_command_line函数

这里首先是解析命令行参数,设计得很精妙。


def parse_command_line(args=None):
    """Parses all options given on the command line.
    We return all command line arguments that are not options as a list.
    """
   # 默认情况下使用sys.argv列表,那么args至少有一个值,就是你脚本的名称。
    if args is None: args = sys.argv
    remaining = []
    # 显然len(args)至少为1,如果为1,表示没有参数,xrange(1, len(args))
    # 生成一个空对象。
    for i in xrange(1, len(args)):
        # All things after the last option are command line arguments
        # 开始的参数如果没有以-开始,那么整个参数都有问题。
        if not args[i].startswith("-"):
            remaining = args[i:]
            break
        # 如果你的参数仅仅是--,后面没有跟参数,那么后面整个参数都有问题。
        if args[i] == "--":
            remaining = args[i+1:]
            break
        # 获取参数名称
        arg = args[i].lstrip("-")   
        name, equals, value = arg.partition("=")
        name = name.replace('-', '_')
        # 如果name不在tornado支持的options中,表示不识别该选项或命令行参数
        if not name in options:
            print_help()
            raise Error('Unrecognized command line option: %r' % name)
        # 获取已经定义的选项值。
        option = options[name]
        # 等号为空字符串,那么表示该命令行参数可能是一个选项
        if not equals:
            #本身是一个选项,打开即可
            if option.type == bool:
                value = "true"
            else:
                #  否则,应该有值
                raise Error('Option %r requires a value' % name)
        option.parse(value)
    # 如果是help命令,打印出help信息,退出即可
    if options.help:
        print_help()
        sys.exit(0)

    # Set up log level and pretty console logging by default
    # 如果设置的是logging,那么设置logging level
    if options.logging != 'none':
        logging.getLogger().setLevel(getattr(logging, options.logging.upper()))
        enable_pretty_logging()

    return remaining

我们用如下代码进行测试:


#!/usr/bin/env/python
import tornado.options 

from tornado.options import parse_command_line

def main():
    """
    """
    args = ["file_name", "--help"]
    parse_command_line(args)

if __name__== "__main__":
    main()

options源码查看

在tornado中有一个已经定义的实例options,我们在parse_command_line中已经看到了,其已经定义了很多tornado支持的选项。我们看看其实例化和_Options类实现:

# options实例化,用的是_Options类的类方法。
options = _Options.instance()

class _Options(dict):
    """Our global program options, an dictionary with object-like access."""
    @classmethod
    def instance(cls):
        if not hasattr(cls, "_instance"):
            cls._instance = cls()
        return cls._instance
    def __getattr__(self, name):
        # 如果值对象类型为_Option,那么就返回其值。
        if isinstance(self.get(name), _Option):
            return self[name].value()
        raise AttributeError("Unrecognized option %r" % name)options = _Options.instance()

我们看看,_Options实际上是一个字典,其定义如上,使用了一个单例模式,注意到hasattr的使用,以及__getattr__魔法方法的使用。我们看看值类型_Option定义:


class _Option(object):
    def __init__(self, name, default=None, type=str, help=None, metavar=None,
                 multiple=False, file_name=None):
        if default is None and multiple:
            default = []
        self.name = name
        self.type = type
        self.help = help
        self.metavar = metavar
        self.multiple = multiple
        self.file_name = file_name
        self.default = default
        # 默认值为None
        self._value = None

_Option的parse方法

在parse_command_line中有一句option.parse(value),其根据将参数传入的值,设置option为该值。


def parse(self, value):
        # 注意这种字典方式
        # 其作用是根据参数的类型(比如:是一个简单的选项,或者其值应该为字符串)
        # 选择相应的解析函数。
        _parse = {
            # 
            datetime.datetime: self._parse_datetime,
            datetime.timedelta: self._parse_timedelta,
            bool: self._parse_bool,
            str: self._parse_string,
        }.get(self.type, self.type)
        # 值有多个
        if self.multiple:
            if self._value is None:
                self._value = []
            # 获取每个
            for part in value.split(","):
                if self.type in (int, long):
                    # allow ranges of the form X:Y (inclusive at both ends)
                    lo, _, hi = part.partition(":")
                    # 对于int, long type,_parse返回的是int()和long()方法,
                    # 很巧妙
                    lo = _parse(lo)
                    hi = _parse(hi) if hi else lo
                    # 设置添加多个系列值
                    self._value.extend(range(lo, hi+1))
                else:
                    self._value.append(_parse(part))
        else:
            self._value = _parse(value)
        return self.value()

_parse = { xxxx}.get(self.type, self.type)有点继承的味道,根据传入值的类型,来选择相应的解析方式。实现的比较优雅,值得学习。

define函数

我们知道tornado实际上在项目中,在options添加了几个默认的_Option对象。

define("help", type=bool, help="show this help information")
define("logging", default="info",
       help=("Set the Python log level. If 'none', tornado won't touch the "
             "logging configuration."),
       metavar="info|warning|error|none")
define("log_to_stderr", type=bool, default=None,
       help=("Send log output to stderr (colorized if possible). "
             "By default use stderr if --log_file_prefix is not set and "
             "no other logging is configured."))
define("log_file_prefix", type=str, default=None, metavar="PATH",
       help=("Path prefix for log files. "
             "Note that if you are running multiple tornado processes, "
             "log_file_prefix must be different for each of them (e.g. "
             "include the port number)"))
define("log_file_max_size", type=int, default=100 * 1000 * 1000,
       help="max size of log files before rollover")
define("log_file_num_backups", type=int, default=10,
       help="number of log files to keep")

具体实现:

  if name in options:
        # 重复定义不允许
        raise Error("Option %r already defined in %s", name,
                    options[name].file_name)
    frame = sys._getframe(0)
    # 用来获取当前文件名
    options_file = frame.f_code.co_filename
    file_name = frame.f_back.f_code.co_filename
    # 如果是在options.py使用了define这个函数,
    # 那么file_name就默认为空
    if file_name == options_file: file_name = ""
    options[name] = _Option(name, file_name=file_name, default=default,
                            type=type, help=help, metavar=metavar,
                            multiple=multiple)

sys._getframe(0)表示调用栈帧顶部,也就是define这个函数。下面的frame.f_code.co_filename表示定义define这个函数脚本名称(包含路径), 后面的frame.f_back.f_code.co_filename表示调用define函数的脚本名称(包含路径)。比较冷门的使用。下面是,调试结果,我们在helloworld.py中使用了define函数,结果如下:

posted @ 2017-08-13 12:31  BruceChen7  阅读(594)  评论(0编辑  收藏  举报