Python自动重新加载模块(autoreload module)

守护进程模式

  使用python开发后台服务程序的时候,每次修改代码之后都需要重启服务才能生效比较麻烦。看了一下Python开源的Web框架(Django、Flask等)都有自己的自动加载模块功能(autoreload.py),都是通过subprocess模式创建子进程,主进程作为守护进程,子进程中一个线程负责检测文件是否发生变化,如果发生变化则退出,主进程检查子进程的退出码(exist code)如果与约定的退出码一致,则重新启动一个子进程继续工作。

自动重新加载模块代码如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""This module is used to test how to reload the modules automatically when any
changes is detected.
"""
__author__="Wenjun Xiao"

import os,sys,time,subprocess,thread

def iter_module_files():
    for module in sys.modules.values():
        filename = getattr(module, ‘__file__‘, None)
        if filename:
            if filename[-4:] in (‘.pyo‘, ‘.pyc‘):
                filename = filename[:-1]
            yield filename

def is_any_file_changed(mtimes):
    for filename in iter_module_files():
        try:
            mtime = os.stat(filename).st_mtime
        except IOError:
            continue
        old_time = mtimes.get(filename, None)
        if old_time is None:
            mtimes[filename] = mtime
        elif mtime > old_time:
            return 1
    return 0

def start_change_detector():
    mtimes = {}
    while 1:
        if is_any_file_changed(mtimes):
            sys.exit(3)
        time.sleep(1)

def restart_with_reloader():
    while 1:
        args = [sys.executable] + sys.argv
        new_env = os.environ.copy()
        new_env[‘RUN_FLAG‘] = ‘true‘
        exit_code = subprocess.call(args, env=new_env)
        if exit_code != 3:
            return exit_code

def run_with_reloader(runner):
    if os.environ.get(‘RUN_FLAG‘) == ‘true‘:
        thread.start_new_thread(runner, ())
        try:
            start_change_detector()
        except KeyboardInterrupt:
            pass
    else:
        try:
            sys.exit(restart_with_reloader())
        except KeyboardInterrupt:
            pass

autoreload.py

测试的主模块如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Runner for testing autoreload module."""

__author__="Wenjun Xiao"

import os,time

def runner():
    print "[%s]enter..." % os.getpid()
    while 1:
        time.sleep(1)
    print "[%s]runner." % os.getpid()

if __name__ == ‘__main__‘:
    from autoreload import run_with_reloader
    run_with_reloader(runner)

runner.py

运行runner.py:

[email protected]:python-autoreload$ python runner.py
[11743]enter...

主程序已经运行,只不过是一致在循环,可以查看此时有两个进程:

[email protected]:~$ ps -aux|grep runner[.py]
promiss+ 11742  0.0  0.2  10928  4208 pts/0    S+   19:34   0:00 python runner.py
promiss+ 11743  0.0  0.1  20152  4092 pts/0    Sl+  19:34   0:00 /usr/bin/python runner.py

在编辑器中打开runner.py做一些可见的修改(增加一条打印语句)如下:

# runner.py
...
def runner():
    print "[%s]enter..." % os.getpid()
    print "[%s]Runner has changed." % os.getpid()
    while 1:
        time.sleep(1)
    print "[%s]runner." % os.getpid()
...

保存之后查看运行运行情况:

[email protected]:python-autoreload$ python runner.py
[11743]enter...
[11772]enter...
[11772]Runner has changed.

可以看到新增的语句已经生效,继续看进程情况:

[email protected]:~$ ps -aux|grep runner[.py]
promiss+ 11742  0.0  0.2  10928  4220 pts/0    S+   19:34   0:00 python runner.py
promiss+ 11772  0.0  0.1  20152  4092 pts/0    Sl+  19:37   0:00 /usr/bin/python runner.py

可以对比两次的进程,可以看到使用守护进程模式可以简单的实现模块自动重新加载功能。

使用守护进程模式,有一种情况比较麻烦:如果主进程由于其他原因退出了,那么子进程还在运行:

[email protected]:~$ kill 11742
[email protected]:~$ ps -aux|grep runner[.py]
promiss+ 11772  0.0  0.1  20152  4092 pts/0    Sl   19:37   0:00 /usr/bin/python runner.py

为了重启服务还需要通过其他方式找到子进程并结束它可以。

守护进程模式-退出问题

  为了解决由于守护进程退出,而导致子进程没有退出的问题,一种比较简单的解决方法就是在守护进程退出的时候也把子进程结束:

# autoreload.py
...
import signal
...
_sub_proc = None

def signal_handler(*args):
    global _sub_proc
    if _sub_proc:
        print "[%s]Stop subprocess:%s" % (os.getpid(), _sub_proc.pid)
        _sub_proc.terminate()
    sys.exit(0)

def restart_with_reloader():
    signal.signal(signal.SIGTERM, signal_handler)     while 1:
        args = [sys.executable] + sys.argv
        new_env = os.environ.copy()
        new_env[‘RUN_FLAG‘] = ‘true‘
        global _sub_proc
        _sub_proc = subprocess.Popen(args, env=new_env)
        exit_code = _sub_proc.wait()        if exit_code != 3:
            return exit_code
...

运行,查看效果(这次没有测试修改):

[email protected]:python-autoreload$ python runner.py
[12425]enter...
[12425]Runner has changed.
[12424]Stop subprocess:12425

另一个控制台执行的命令如下:

[email protected]:~$ ps -aux|grep runner[.py]
promiss+ 12424  0.2  0.2  10928  4224 pts/0    S+   20:26   0:00 python runner.py
promiss+ 12425  0.2  0.1  20152  4092 pts/0    Sl+  20:26   0:00 /usr/bin/python runner.py
[email protected]:~$ kill 12424
[email protected]:~$ ps -aux|grep runner[.py]
[email protected]:~$ 

已经达到我们需要的功能了吗?等等,在控制台上运行工程总是能很好的工作,如果是在IDE中呢?由于IDE中输入输出是重定向处理的,比如,在Sublime中就没有办法获取到输出信息。

因此还需要进一步完善输出的问题。

守护进程模式-输出问题

解决输出问题,也很简单,修改如下:

# autoreload.py
...
def restart_with_reloader():
    signal.signal(signal.SIGTERM, signal_handler)
    while 1:
        args = [sys.executable] + sys.argv
        new_env = os.environ.copy()
        new_env[‘RUN_FLAG‘] = ‘true‘
        global _sub_proc
        _sub_proc = subprocess.Popen(args, env=new_env, stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT)
        read_stdout(_sub_proc.stdout)
        exit_code = _sub_proc.wait()
        if exit_code != 3:
            return exit_code

...
def read_stdout(stdout):
    while 1:
        data = os.read(stdout.fileno(), 2**15)
        if len(data) > 0:
            sys.stdout.write(data)
        else:
            stdout.close()
            sys.stdout.flush()
            break

经过以上修改,也适合在IDE中使用守护进程模式了。

源代码:https://github.com/wenjunxiao/python-autoreload

时间: 2024-10-05 21:07:12

Python自动重新加载模块(autoreload module)的相关文章

python如何重新加载模块

Python如何重新加载模块?Python教程(http://www.maiziedu.com/course/python/)中重新加载模块的方法有哪些呢?在python开发中,我们为了防止两个模块互相导入的问题,Python默认所有的模块都只导入一次,可以在开发时,我们会需要重新导入模块,那么怎么办呢,下面一起看看python重新加载模块的几种方法吧: Python2.7可以直接用reload(),Python3可以用下面几种方法: 方法一:基本方法 from imp import reloa

Python importlib 动态加载模块

# 创建一个 src 文件夹,里面有一个 commons.py 文件,内容如下 def add(): print("add ....") # 创建一个 app.py 文件,内容如下: module = 'src.commons' func_name = 'add' import importlib m = importlib.import_module(module) print(m) func = getattr(m, func_name) func() # 运行 app.py ,结

Python动态加载模块

需求:实现一个简单的pyton程序,接收两个参数:plugin_name, data,根据不同的plugin_name定位到不同的逻辑处理模块并进行输出. 实现方案: 使用python的库函数:load_source,将插件模块加载到一个dict中key为模块名称,value为类的实例,核心代码: def load_plugins(): global plugin_dict # 遍历插件目录加载所有py结尾的模块 for root, dirs, files in os.walk(module_p

python 在初始化运行环境时所有预加载模块

python 在初始化运行环境时,会预加载一批内建模块到内存. 所有预加载模块如下: 1 >>> for item in sys.modules.items(): 2 ... print item 3 ... 4 ('copy_reg', <module 'copy_reg' from '/Users/Kris/git_space/pyv/lib/python2.7/copy_reg.pyc'>) 5 ('encodings', <module 'encodings'

自研模块加载器(三) module模块构造器设计-模块数据初始化

依赖加载策略 模块数据初始化 status状态生命周期 代码展示 demo包括4个文件, index.html , a.js , b.js , startUp.js index.html <!DOCTYPE html> <html> <head> <title>自研模块加载器</title> </head> <body> <script src="./startUp.js"></scr

第四十天:编译可加载模块

linux刚刚开始的时候仅仅支持intel 386 ,后来不断的被移植到越来越多的平台上,包括ARM ,POWERPC,所有的代码设备驱动代码都编译到内核中,这明显不现实,这时候就需要通过内核模块的形式来加载驱动.当然模块不一定是驱动,也可以是为驱动提供某种功能. 现在先编写一个简单的linux模块. 1 #include <linux/init.h> 2 #include <linux/module.h> 3 4 MODULE_LICENSE("GPL");

AngularJs 动态加载模块和依赖注入

最近项目比较忙额,白天要上班,晚上回来还需要做Angular知识点的ppt给同事,毕竟年底要辞职了,项目的后续开发还是需要有人接手的,所以就占用了晚上学习的时间.本来一直不打算写这些第三方的学习笔记,不过觉得按需加载模块并且成功使用这个确实是个好处,还是记录下来吧.基于本兽没怎么深入的使用requireJs,所以本兽不知道这个和requireJs有什么区别,也不能清晰的说明这到底算不算Angular的按需加载. 为了实现这篇学习笔记知识点的效果,我们需要用到: angular:https://g

AngularJS中多个ng-app(手动加载模块)

1.当有多个ng-app时:(首先是要加载angularJS) <div ng-app=""> <p>姓名:<input type="text" ng-model="name" placeholder="请输入姓名" /></p> <p> {{name}} </p> </div> <div ng-app="">

关于AMD(异步加载模块)和CMD(同步加载模块),require.js

1.CommonJS,有一个全局性方法require(),用于加载模块.假定有一个数学模块math.js,就可以像下面这样加载. var math = require('math'); 然后,就可以调用模块提供的方法: var math = require('math'); math.add(2,3); // 5 第二行math.add(2, 3),在第一行require('math')之后运行,因此必须等math.js加载完成.也就是说,如果加载时间很长,整个应用就会停在那里等. 这对服务器端