• 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函数,结果如下:

  • 相关阅读:
    Golang 学习之路
    Kubernetes 资源对象之DaemonSet
    Spring Cloud服务注册中心交付至kubernetes
    自动化运维之Ansible入门
    MySQL 高可用之主从复制
    Kubernetes 强大的namespace
    SaltStack RESTful API操作
    Kubernetes 服务自动发现CoreDNS
    2个月……
    一次游玩与这两周的一些感想
  • 原文地址:https://www.cnblogs.com/bofengqiye/p/7352971.html
Copyright © 2020-2023  润新知