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

     当我们在使用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 modules
if self.settings.get('autoreload'):
    from tornado import autoreload
    autoreload.start()

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

import tornado.autoreload

app = 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 os
import time
stat = 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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值