浅析 Django runserver 的 autoreload 功能

by z9g

在一个django项目中用python manange.py runserver启动一个内置server以后, 当修改了这个项目的某个python文件,内置的server会自动重启以加载新的文件,这个功能看上去很cool, 大大节省了开发调试过程中手动重启server的时间。 今天抽空看了一下其实现的代码, 发现原来这个autoreload的功能是可以完全独立于django的一个模块。
runserver是django management的一个command, 源文件位置在 django目录/core/management/commands/runserver.py, 在文件的最后可以看到这样几行代码:

if use_reloader:
     from django.utils import autoreload
     autoreload.main(inner_run)
else :
     inner_run()

不难看出, 真正干活的其实是inner_run, 而负责自动重启server的是django.utils里面的autoreload.
是否reload的判断条件都是检查sys.modules中各个模块文件的最后修改时间是否有所变化, 但是由于python和jython的线程机制的不同,autoreload.py针对python和jython做了不同的处理, jython reloade的实现:

def jython_reloader(main_func, args, kwargs):
     from _systemrestart import SystemRestart
     thread.start_new_thread(main_func, args)
     while True :
         if code_changed():
             raise SystemRestart
         time.sleep( 1 )

相比较python环境下的实现要稍微发杂些, python_reloader 设置一个环境变量RUN_MAIN,然后复制一个当前进程的子进程(不是线程哦), 然后在这个新进程中启动一个线程去真正干活儿,一个线程检查文件是否修改,当有文件修改的时候, 退出新启动的进程( sys.exit(3) ), 这时父进程会判断是否是因为修改文件导致(返回码为 3), 是则再启动一个新进程去干活儿, 否则整个程序退出。
具体细节还是看代码更清晰一些, 整个autoreload的代码跟django没有耦合, 可以单独使用。
如是做了如下尝试:

from django.utils import autoreload
 
def tt():
     print 'aaa...'
     while True : pass
 
autoreload.main(tt)

保存,然后在命令行运行之,期间修改源码,将print ‘aaa…’改成print ‘bbb…’, 保存, 可以看到之前运行的程序输出为:

$ python tt.py
aaa...
bbb...

可见在调试一个daemon程序的时候,通过autoreload加入自动加载功能是多么easy的事情了 :)

补充:之后又看了以下webpy的内置server的reload功能的实现,在webpy的application.py中能找到reload的时间, 大致原理是每个request都做一下是否reload的检查, 耦合度比较紧。