浅谈django开发者模式中的autoreload是如何实现的


Posted in Python onAugust 18, 2017

在开发django应用的过程中,使用开发者模式启动服务是特别方便的一件事,只需要 python manage.py runserver 就可以运行服务,并且提供了非常人性化的autoreload机制,不需要手动重启程序就可以修改代码并看到反馈。刚接触的时候觉得这个功能比较人性化,也没觉得是什么特别高大上的技术。后来有空就想着如果是我来实现这个autoreload会怎么做,想了很久没想明白,总有些地方理不清楚,看来第一反应真是眼高手低了。于是就专门花了一些时间研究了django是怎样实现autoreload的,每一步都看源码说话,不允许有丝毫的想当然:

1、runserver命令。在进入正题之前其实有一大段废话,是关于runserver命令如何执行的,和主题关系不大,就简单带一下:

命令行键入 python manage.py runserver 后,django会去寻找runserver这个命令的执行模块,最后落在

django\contrib\staticfiles\management\commands\runserver.py模块上:

#django\contrib\staticfiles\management\commands\runserver.py
from django.core.management.commands.runserver import \
Command as RunserverCommand

class Command(RunserverCommand):
help = "Starts a lightweight Web server for development and also serves static files."

而这个Command的执行函数在这:

#django\core\management\commands\runserver.py
class Command(BaseCommand):
def run(self, **options):

"""

Runs the server, using the autoreloader if needed

"""

use_reloader = options['use_reloader']


if use_reloader:


autoreload.main(self.inner_run, None, options)

else:


self.inner_run(None, **options)

这里有关于use_reloader的判断。如果我们在启动命令中没有加--noreload,程序就会走autoreload.main这个函数,如果加了,就会走self.inner_run,直接启动应用。

其实从autoreload.main的参数也可以看出,它应该是对self.inner_run做了一些封装,autoreload的机制就在这些封装当中,下面我们继续跟。

PS: 看源码的时候发现django的command模式还是实现的很漂亮的,值得学习。

2、autoreload模块。看autoreload.main():

#django\utils\autoreload.py:
def main(main_func, args=None, kwargs=None):
if args is None:


args = ()

if kwargs is None:


kwargs = {}

if sys.platform.startswith('java'):


reloader = jython_reloader

else:


reloader = python_reloader


wrapped_main_func = check_errors(main_func)

reloader(wrapped_main_func, args, kwargs)

这里针对jpython和其他python做了区别处理,先忽略jpython;check_errors就是把对main_func进行错误处理,也先忽略。看python_reloader:

#django\utils\autoreload.py:
def python_reloader(main_func, args, kwargs):
if os.environ.get("RUN_MAIN") == "true":


thread.start_new_thread(main_func, args, kwargs)


try:



reloader_thread()


except KeyboardInterrupt:



pass

else:


try:



exit_code = restart_with_reloader()



if exit_code < 0:




os.kill(os.getpid(), -exit_code)



else:




sys.exit(exit_code)


except KeyboardInterrupt:



pass

第一次走到这里时候,环境变量中RUN_MAIN变量不是"true", 甚至都没有,所以走else, 看restart_with_reloader:

#django\utils\autoreload.py:
def restart_with_reloader():
 while True:
 args = [sys.executable] + ['-W%s' % o for o in sys.warnoptions] + sys.argv


if sys.platform == "win32":



args = ['"%s"' % arg for arg in args]


new_environ = os.environ.copy()


new_environ["RUN_MAIN"] = 'true'


exit_code = os.spawnve(os.P_WAIT, sys.executable, args, new_environ)


if exit_code != 3:



return exit_code

这里首先起一个while循环, 内部先把RUN_MAIN改成了"true",然后用os.spawnve方法开一个子进程(subprocess),看看os.spawnve的说明:

#os.py
def spawnve(mode, file, args, env):
"""spawnve(mode, file, args, env) -> integer


Execute file with arguments from args in a subprocess with the

specified environment.

If mode == P_NOWAIT return the pid of the process.

If mode == P_WAIT return the process's exit code if it exits normally;

otherwise return -SIG, where SIG is the signal that killed it. """


return _spawnvef(mode, file, args, env, execve)

其实就是再调一遍命令行,又走了一遍 python manage.py runserver。

接着看restart_with_reloader里的while循环,需要注意的是while循环退出的唯一条件是exit_code!=3。 如果子进程不退出,就一直停在 os.spawnve这一步; 如果子进程退出,而退出码不是3,while就被终结了;如果是3,继续循环,重新创建子进程。从这个逻辑可以猜想autoreload的机制:当前进程(主进程)其实啥也不干,就监视子进程的运行状况,子进程才是真正干事儿的;如果子进程以exit_code=3退出(应该由于检测到了文件修改),就再启动一遍子进程,新代码自然就生效了;如果子进程以exit_code!=3退出,主进程也结束,整个django程序就算跪了。这只是猜想,下面接着来验证。

3、子进程。上面其实有一个疑问,既然是重新启动了一次,为什么子进程不会接着生成子进程?原因就在于RUN_MAIN这个环境变量,主进程中把它改成了true,子进程走到python_reloader函数的时候:

#django\utils\autoreload.py:
def python_reloader(main_func, args, kwargs):
if os.environ.get("RUN_MAIN") == "true":


thread.start_new_thread(main_func, args, kwargs)


try:



reloader_thread()


except KeyboardInterrupt:



pass

else:


try:



exit_code = restart_with_reloader()



if exit_code < 0:




os.kill(os.getpid(), -exit_code)



else:




sys.exit(exit_code)


except KeyboardInterrupt:



pass

if条件满足了,和主进程走了不一样的逻辑分支。在这里,首先去开一个线程,运行main_func,就是上文的 Command.inner_run。这里的thread模块是这么import的:

#django\utils\autoreload.py:
from django.utils.six.moves import _thread as thread

这里six模块的作用是兼容各种python版本:

[codeblock six]
#django\utils\six.py
class _SixMetaPathImporter(object):

"""
A meta path importer to import six.moves and its submodules.

This class implements a PEP302 finder and loader. It should be compatible
with Python 2.5 and all existing versions of Python3
"""

官网说明:
# https://pythonhosted.org/six/
Six: Python 2 and 3 Compatibility Library
Six provides simple utilities for wrapping over differences between Python 2 and Python 3. It is intended to support codebases that work on both Python 2 and 3 without modification. six consists of only one Python file, so it is painless to copy into a project.

所以如果程序想在python2和python3上都能跑,且鲁邦,six是重要的工具。之后抽个时间看下six,mark一下。

然后再开一个reloader_thread:

[codeblock autoreload_reloader_thread]
#django\utils\autoreload.py:
def reloader_thread():
ensure_echo_on()

if USE_INOTIFY:


fn = inotify_code_changed

else:


fn = code_changed


while RUN_RELOADER:


change = fn()


if change == FILE_MODIFIED:



sys.exit(3) # force reload


elif change == I18N_MODIFIED:



reset_translations()


time.sleep(1)

ensure_echo_on()其实还没看明白,貌似是针对类unix系统文件处理的,先略过;
USE_INOTIFY也是系统文件操作相关的变量,根据 inotify 是否可用选择检测文件变化的方法。
while循环,每隔1秒检测一下文件状态,如果是普通文件有变化,进程退出,退出码为3,主进程一看:退出码是3,就重启子进程。。。。这样就和上面连上了;如果不是普通文件变化,而是I18N_MODIFIED(.mo后缀的文件变化,二进制库文件之类的),那就 reset_translations ,大概意思是把已加载过的库缓存清理掉,下次重新加载。

以上就是autoreload机制的流程。其中还是有些细节不是特别清楚,比如不同操作系统文件变化的检测,但都是很细节的东西了,不涉及主流程。看完这些,我又问了自己一遍,如果是让我设计autoreload机制会怎样搞。现在我的答案是:直接把 django\utils\autoreload.py 文件拿来用啊。其实这是很独立的一个模块,而且特别通用,完全可以作为通用的autoreload解决方案,我还自己写个毛啊。

这篇浅谈django开发者模式中的autoreload是如何实现的就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python实现类继承实例
Jul 04 Python
Java多线程编程中ThreadLocal类的用法及深入
Jun 21 Python
python3.0 模拟用户登录,三次错误锁定的实例
Nov 02 Python
Python cookbook(数据结构与算法)从序列中移除重复项且保持元素间顺序不变的方法
Mar 13 Python
在python中实现强制关闭线程的示例
Jan 22 Python
Python中的正则表达式与JSON数据交换格式
Jul 03 Python
Python使用grequests(gevent+requests)并发发送请求过程解析
Sep 25 Python
python3 图片 4通道转成3通道 1通道转成3通道 图片压缩实例
Dec 03 Python
python的reverse函数翻转结果为None的问题
May 11 Python
Selenium alert 弹窗处理的示例代码
Aug 06 Python
详解pycharm2020.1.1专业版安装指南(推荐)
Aug 07 Python
python中reload重载实例用法
Dec 15 Python
Python绑定方法与非绑定方法详解
Aug 18 #Python
python字典DICT类型合并详解
Aug 17 #Python
Python时间的精准正则匹配方法分析
Aug 17 #Python
Python实现运行其他程序的四种方式实例分析
Aug 17 #Python
python进阶_浅谈面向对象进阶
Aug 17 #Python
Python 比较两个数组的元素的异同方法
Aug 17 #Python
python使用opencv读取图片的实例
Aug 17 #Python
You might like
收音机的保养
2021/03/01 无线电
PHP 加密解密内部算法
2010/04/22 PHP
推荐一本PHP程序猿都应该拜读的书
2014/12/31 PHP
js代码实现微博导航栏
2015/07/30 PHP
PHP生成各种随机验证码的方法总结【附demo源码】
2017/06/05 PHP
Laravel学习教程之路由模块
2017/08/18 PHP
怎么用javascript进行拖拽
2006/07/20 Javascript
可以文本显示的公告栏的js代码
2007/03/11 Javascript
jquery移除button的inline onclick事件(已测试及兼容浏览器)
2013/01/25 Javascript
JavaScript中统计Textarea字数并提示还能输入的字符
2014/06/10 Javascript
node.js中的fs.chmodSync方法使用说明
2014/12/18 Javascript
freemarker判断对象是否为空的方法
2015/08/13 Javascript
js变量提升深入理解
2016/09/16 Javascript
Vue.js双向绑定操作技巧(初级入门)
2016/12/27 Javascript
微信小程序 两种滑动方式(横向滑动,竖向滑动)详细及实例代码
2017/01/13 Javascript
关于不同页面之间实现参数传递的几种方式讨论
2017/02/13 Javascript
ES6新特性之Symbol类型用法分析
2017/03/31 Javascript
jQuery实现动态添加、删除按钮及input输入框的方法
2017/04/27 jQuery
tween.js缓动补间动画算法示例
2018/02/13 Javascript
node.js的Express服务器基本使用教程
2019/01/09 Javascript
node微信开发之获取access_token+自定义菜单
2019/03/17 Javascript
JavaScript使用面向对象实现的拖拽功能详解
2019/06/12 Javascript
p5.js实现动态图形临摹
2019/10/23 Javascript
Python中不同进制互相转换(二进制、八进制、十进制和十六进制)
2015/04/05 Python
Python中字典的基本知识初步介绍
2015/05/21 Python
Python的多态性实例分析
2015/07/07 Python
用python 批量更改图像尺寸到统一大小的方法
2018/03/31 Python
Pandas中把dataframe转成array的方法
2018/04/13 Python
python对execl 处理操作代码
2020/06/22 Python
CSS3基础(RGBa、text-shadow、box-shadow、border-radius)
2012/11/13 HTML / CSS
HTML5调用手机摄像头拍照的实现思路及代码
2014/06/15 HTML / CSS
毕业生医学检验求职信
2013/10/16 职场文书
计划生育标语
2014/06/23 职场文书
Javascript之datagrid查询详解
2021/09/15 Javascript
Vue中Object.assign清空数据报错的解决方案
2022/03/03 Vue.js
win10重装系统后上不了网怎么办 win10重装系统网络故障的解决办法
2022/07/23 数码科技