Python自動從新加載模塊(autoreload module)

守護進程模式

  使用python開發後臺服務程序的時候,每次修改代碼以後都須要重啓服務才能生效比較麻煩。看了一下Python開源的Web框架(Django、Flask等)都有本身的自動加載模塊功能(autoreload.py),都是經過subprocess模式建立子進程,主進程做爲守護進程,子進程中一個線程負責檢測文件是否發生變化,若是發生變化則退出,主進程檢查子進程的退出碼(exist code)若是與約定的退出碼一致,則從新啓動一個子進程繼續工做。python

自動從新加載模塊代碼以下:git

#!/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

測試的主模塊以下:github

#!/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:ubuntu

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.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()
...

保存以後查看運行運行狀況:ide

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.py
promiss+ 11772  0.0  0.1  20152  4092 pts/0    Sl+  19:37   0:00 /usr/bin/python runner.py

能夠對比兩次的進程,能夠看到使用守護進程模式能夠簡單的實現模塊自動從新加載功能。spa

使用守護進程模式,有一種狀況比較麻煩:若是主進程因爲其餘緣由退出了,那麼子進程還在運行:線程

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

運行,查看效果(此次沒有測試修改):

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.py
promiss+ 12425  0.2  0.1  20152  4092 pts/0    Sl+  20:26   0:00 /usr/bin/python runner.py
promissing@ubuntu:~$ kill 12424
promissing@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

相關文章
相關標籤/搜索