1. 程式人生 > 程式設計 >Python自動重新載入模組詳解(autoreload module)

Python自動重新載入模組詳解(autoreload module)

守護程序模式

使用python開發後臺服務程式的時候,每次修改程式碼之後都需要重啟服務才能生效比較麻煩。

看了一下Python開源的Web框架(Django、Flask等)都有自己的自動載入模組功能(autoreload.py),都是通過subprocess模式建立子程序,主程序作為守護程序,子程序中一個執行緒負責檢測檔案是否發生變化,如果發生變化則退出,主程序檢查子程序的退出碼(exist code)如果與約定的退出碼一致,則重新啟動一個子程序繼續工作。

自動重新載入模組程式碼如下:

autoreload.py

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

測試的主模組如下:

runner.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:

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

儲存之後檢視執行執行情況:

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

可以對比兩次的程序,可以看到使用守護程序模式可以簡單的實現模組自動重新載入功能。

使用守護程序模式,有一種情況比較麻煩:如果主程序由於其他原因退出了,那麼子程序還在執行:

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

以上這篇Python自動重新載入模組詳解(autoreload module)就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支援我們。