Tornado Application对象实例化时"debug=Ture"参数的作用原理

来源:互联网 发布:有什么金融软件 编辑:程序博客网 时间:2024/06/06 00:00

     当我们在使用Tornado框架编写程序的时候,使用Tornado自带的调试模式可以让我们的工作轻松不少。因为这样我们就不必要在每次修改程序后手动重启服务器。

      开启Tornado的方法就是在实例化Application对象的时候加入"debug=True"参数,示例代码:

app = tornado.web.Application(    handlers=[(r'/', IndexHandler)],    debug=True)

      那么,加入"debug=True"参数后代码实现自动编译的原理是什么呢?查看Tornado web.py的源代码,找到Application类的定义,我们看到Application类的构造函数:

def __init__(self, handlers=None, default_host="", transforms=None,             **settings)

      其中:

if self.settings.get('debug'):    self.settings.setdefault('autoreload', True)    self.settings.setdefault('compiled_template_cache', False)    self.settings.setdefault('static_hash_cache', False)    self.settings.setdefault('serve_traceback', True)# Automatically reload modified modulesif self.settings.get('autoreload'):    from tornado import autoreload    autoreload.start()

      可以知道,如果我们在Application实例化的时候加入"debug=True"参数,程序import了autoreload模块,并且调用了autoreload.start()函数来实现自动重载。事实上,我们可以有开启Tornado调试模式的第二种写法:

import tornado.autoreloadapp = tornado.web.Application(    handlers=[(r'/', IndexHandler)])http_server = tornado.httpserver.HTTPServer(app)http_server.listen(options.port)instance = tornado.ioloop.IOLoop.instance()tornado.autoreload.start(instance)instance.start()

      现在,我们来看一下,autoreload.start()又是如何实现程序的自动重载的。

      查看Tornado的autoreload.py文件,看到start函数:

def start(io_loop=None, check_time=500):    """Begins watching source files for changes using the given `.IOLoop`. """    io_loop = io_loop or ioloop.IOLoop.current()    if io_loop in _io_loops:        return    _io_loops[io_loop] = True    if len(_io_loops) > 1:        gen_log.warning("tornado.autoreload started more than once in the same process")    add_reload_hook(functools.partial(io_loop.close, all_fds=True))    modify_times = {}    callback = functools.partial(_reload_on_update, modify_times)    scheduler = ioloop.PeriodicCallback(callback, check_time, io_loop=io_loop)    scheduler.start()

       函数的功能注释已经写得很清楚了,就是循环监视 '.IOLoop'的源文件,默认时间500ms。这个函数通过使用PeriodicCallback类实例化一个scheduler对象,并且根据PeriodicCallback的特性循环调用一个callback函数。而callback函数则是由偏函数functools.partial以函数_reload_on_update, 集合modify_times为参数生成的(函数式编程的思想)。

      查看_reload_on_update的源代码:

def _reload_on_update(modify_times):    if _reload_attempted:        # We already tried to reload and it didn't work, so don't try again.        return    if process.task_id() is not None:        # We're in a child process created by fork_processes.  If child        # processes restarted themselves, they'd all restart and then        # all call fork_processes again.        return    for module in sys.modules.values():        # Some modules play games with sys.modules (e.g. email/__init__.py        # in the standard library), and occasionally this can cause strange        # failures in getattr.  Just ignore anything that's not an ordinary        # module.        if not isinstance(module, types.ModuleType):            continue        path = getattr(module, "__file__", None)        if not path:            continue        if path.endswith(".pyc") or path.endswith(".pyo"):            path = path[:-1]        _check_file(modify_times, path)    for path in _watched_files:        _check_file(modify_times, path)

    它用_check_file函数检查sys.modules里的各种py文件,如果文件的os.stat时间戳改变,那么就执行_reload()重新编译加载,_check_file函数代码:

def _check_file(modify_times, path):    try:        modified = os.stat(path).st_mtime    except Exception:        return    if path not in modify_times:        modify_times[path] = modified        return    if modify_times[path] != modified:        gen_log.info("%s modified; restarting server", path)        _reload()

    这里,modified = os.stat(path).st_mtime就是获取路径为path的文件最后被修改的时间。

#一个使用os.stat来获取文件创建时间和最近修改时间的小例子import osimport timestat = os.stat(path)print "%s" % time.ctime(stat.st_ctime)print "%s" % time.ctime(stat.st_mtime)

     通过比较在这一次循环中文件的st_mtime与上次循环的st_mtime,来发现文件是否被修改过。如果发现有文件被修改,就restarting server。看到_reload()函数的源代码:

def _reload():    global _reload_attempted    _reload_attempted = True    for fn in _reload_hooks:        fn()    if hasattr(signal, "setitimer"):        # Clear the alarm signal set by        # ioloop.set_blocking_log_threshold so it doesn't fire        # after the exec.        signal.setitimer(signal.ITIMER_REAL, 0, 0)    # sys.path fixes: see comments at top of file.  If sys.path[0] is an empty    # string, we were (probably) invoked with -m and the effective path    # is about to change on re-exec.  Add the current directory to $PYTHONPATH    # to ensure that the new process sees the same path we did.    path_prefix = '.' + os.pathsep    if (sys.path[0] == '' and            not os.environ.get("PYTHONPATH", "").startswith(path_prefix)):        os.environ["PYTHONPATH"] = (path_prefix +                                    os.environ.get("PYTHONPATH", ""))    if sys.platform == 'win32':        # os.execv is broken on Windows and can't properly parse command line        # arguments and executable name if they contain whitespaces. subprocess        # fixes that behavior.        subprocess.Popen([sys.executable] + sys.argv)        sys.exit(0)    else:        try:            os.execv(sys.executable, [sys.executable] + sys.argv)        except OSError:            # Mac OS X versions prior to 10.6 do not support execv in            # a process that contains multiple threads.  Instead of            # re-executing in the current process, start a new one            # and cause the current process to exit.  This isn't            # ideal since the new process is detached from the parent            # terminal and thus cannot easily be killed with ctrl-C,            # but it's better than not being able to autoreload at            # all.            # Unfortunately the errno returned in this case does not            # appear to be consistent, so we can't easily check for            # this error specifically.            os.spawnv(os.P_NOWAIT, sys.executable,                      [sys.executable] + sys.argv)            sys.exit(0)_USAGE = """\Usage:  python -m tornado.autoreload -m module.to.run [args...]  python -m tornado.autoreload path/to/script.py [args...]"""

    在这个函数中,先执行了_reload_hooks中的函数,也就是ioloop.close,然后对文件的路径进行了判断和相关操作,再判断操作系统,如果sys.platform 为'win32'的话,就执行subprocess.Popen([sys.executable] + sys.argv)进行重载,否则行os.spawnv(os.P_NOWAIT, sys.executable, [sys.executable] + sys.argv)进行重载。

    以上大致就是"debug=True"参数的作用原理了。我在解析的过程中,只是简单的根据源码来一步一步追踪 'debug=True'加入后被执行到的函数,由于本人知识水平有限,理解难免有疏漏甚至错误的地方,希望各位多多包涵并为我指出错漏。

    附上tornado.autoreload、tornado.ioloop.PeriodicCallback的源码以及几个相关博客链接:

        tornado.autoreload

         tornado.ioloop.PeriodicCallback

         Tornado中ioloop.py之PeriodicCallback类分析

        python os.stat() 和 stat模块详解

        玩蛇记-使用Tornado构建高性能Web之二-autoreload


0 0
原创粉丝点击