美文网首页Python七号
2 行代码实现修改源码后自动重载

2 行代码实现修改源码后自动重载

作者: somenzz | 来源:发表于2020-06-01 21:07 被阅读0次

    有时候,我感到疲倦,因为,我每修改一处代码,想要看到改动是否生效的时候,我要先 Ctrl CKill 进程,然后重新运行,才能看到结果,改的次数多了,不仅浪费时间,降低效率,还浪费体力。有没有办法做到修改了项目使用的源码文件后,让程序自动重新运行?

    肯定有办法,三方库 watchdog 可以监控文件的新增,删除,和修改,可以在这些事件发生后执行相应的动作,但它不够完美:

    1. 可以对某一路径进行监听,但不能解析项目 import 了哪些文件,import 的文件不在同一路径下,需要手工配置多个路径就很麻烦,不具有通用性。
    2. 不能判断文件是否真正的修改,有时候只是保存下,文件内容并没有变化,此时不应该触发重启。
    3. 如果在统一路径,修改了项目未引用的文件,也会触发重启。

    直到我用了 Django,Django 的 autoreload 机制,完美的解决了上面 3 个问题,改动代码保存后可以立即看到程序的及时反馈,大大提升了 Debug 的效率,堪称神器。

    这么好的神器,能否移植到其他项目上?

    ​能否移植,取决于 autoreload 是否与 Django 松耦合,我们先来看一下它的工作原理。

    1、Django 是怎么自动重载的?

    用过 Django 的朋友都知道,当你执行 python manage.py runserver 后,只要修改了项目用到的文件,Django 会自动重新启动服务,这种及时反馈机制,大大的方便了开发者,可以快速确认自己的修改是否正确,为测试省了不少时间。

    从 Django(Django==3.0.4) 的源码 django/core/management/commands/runserver.py 走起,执行 runserver 命令后就执行了这个函数。

    def run(self, **options):
        """Run the server, using the autoreloader if needed."""
        use_reloader = options['use_reloader']
    
        if use_reloader:
            autoreload.run_with_reloader(self.inner_run, **options)
        else:
            self.inner_run(None, **options)
    

    self.inner_run 是真正干活的,先不管它。执行命令时如果不加 --noreload,就会运行 autoreload.run_with_reloader,我们继续追踪到 django/utils/autoreload.py

    def run_with_reloader(main_func, *args, **kwargs):
        signal.signal(signal.SIGTERM, lambda *args: sys.exit(0))
        try:
            if os.environ.get(DJANGO_AUTORELOAD_ENV) == 'true':
                reloader = get_reloader()
                logger.info('Watching for file changes with %s', reloader.__class__.__name__)
                start_django(reloader, main_func, *args, **kwargs)
            else:
                exit_code = restart_with_reloader()
                sys.exit(exit_code)
        except KeyboardInterrupt:
            pass
    

    函数第一行 signal.SIGTERM 来捕捉用户输入 kill 指令,让程序退出并返回 0。 接下来就是判断环境变量是 DJANGO_AUTORELOAD_ENV 是否为 true,如果是,执行 start_django,否则执行 restart_with_reloader。默认设置情况下,第一次运行时,环境变量是没有设置的,因此会运行 restart_with_reloader

    def restart_with_reloader():
        new_environ = {**os.environ, DJANGO_AUTORELOAD_ENV: 'true'}
        args = get_child_arguments()
        while True:
            p = subprocess.run(args, env=new_environ, close_fds=False)
            if p.returncode != 3:
                return p.returncode
    

    这里先通过 get_child_arguments 获取命令及参数,再进入循环,通过 subprocess.run 来运行 Django 服务,Django 运行的过程中,函数是阻塞在此处的,Django 进程运行结束返回的结果不是 3,程序直接就退出了。

    p = subprocess.run(args, env=new_environ, close_fds=False)
    

    大家猜测下 Django 进程什么时候返回 3 呢? 相信你已经猜到了,就是文件有修改时,Django 进程返回了 3,通过循环,实现重新启动的效果。

    def trigger_reload(filename):
        logger.info('%s changed, reloading.', filename)
        sys.exit(3)
    

    调用这个函数的类为 StatReloader 和 WatchmanReloader,具体的细节见 py37env/lib/python3.7/site-packages/django/utils/autoreload.py

    理解了工作原理后,就可以为我所用了。

    2、autoreload 为我所用

    好在 django.utils.autoreload 和 django 其他模块是松耦合的,不需要修改代码即可可以直接移植到其他项目使用。做法很简单,只需要将 Django 库中 utils 目录下的 autoreload.py 文件复制到自己项目的路径下,再导入使用即可。

    两行代码就可以实现,我这里做了个 demo:

    demo 目录树如下:

    (py37env) ➜  test tree
    .
    ├── autoreload.py
    ├── test.py
    └── test2.py
    
    0 directories, 3 files
    

    test.py 文件内容如下:

    # filename: test.py
    import autoreload
    import test2
    
    def main():
        print("---------------------")
        print("test.main1")
        print("test.main2")
        print("test.main3")
        test2.main()
    
    if __name__ == '__main__':
        autoreload.run_with_reloader(main)
    

    test2.py 文件内容如下:

    def main():
        print("test2.main11")
        print("test2.main22")
        print("test2.main33")
    

    运行 python test.py 后,程序打印了预期的结果,但没有退出,说明 autoreload 内部是以守护进程方式运行主函数 main。修改 test.py test2.py 的任何地方,程序都会重新运行,非常便于调试。如果只保存,未修改任何内容,则程序不会重新运行,非常智能。

    运行结果如下:

    ---------------------
    test.main1
    test.main2
    test.main3
    test2.main11
    test2.main22
    test2.main33
    ---------------------
    test.main1
    test.main2
    test.main3
    test.main4
    test2.main11
    test2.main22
    test2.main33
    test2.main44
    

    视频展示:https://b23.tv/MAqqLK

    源代码我放在了公众号后台,如果不想动手找 Django 源码 autoreload ,可以关注「Python七号」,回复关键词 「autoreload」 下载。

    相关文章

      网友评论

        本文标题:2 行代码实现修改源码后自动重载

        本文链接:https://www.haomeiwen.com/subject/khkezhtx.html