首页 > 代码库 > Python自动重新加载模块(autoreload module)

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 anychanges is detected."""__author__="Wenjun Xiao"import os,sys,time,subprocess,threaddef 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 filenamedef 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 0def 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_codedef 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,timedef 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:

promissing@ubuntu:python-autoreload$ python runner.py [11743]enter...

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

promissing@ubuntu:~$ ps -aux|grep runner[.py]promiss+ 11742  0.0  0.2  10928  4208 pts/0    S+   19:34   0:00 python runner.pypromiss+ 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()...

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

promissing@ubuntu:python-autoreload$ python runner.py [11743]enter...[11772]enter...[11772]Runner has changed.

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

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

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

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

promissing@ubuntu:~$ kill 11742promissing@ubuntu:~$ 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 = Nonedef 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...

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

promissing@ubuntu:python-autoreload$ python runner.py[12425]enter...[12425]Runner has changed.[12424]Stop subprocess:12425

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

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

已经达到我们需要的功能了吗?等等,在控制台上运行工程总是能很好的工作,如果是在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

 

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