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


Posted in Python onApril 01, 2020

守护进程模式

使用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)就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Python 相关文章推荐
理解python多线程(python多线程简明教程)
Jun 09 Python
用Python编写分析Python程序性能的工具的教程
Apr 01 Python
浅谈django开发者模式中的autoreload是如何实现的
Aug 18 Python
简单实现python聊天程序
Apr 01 Python
python实现图书借阅系统
Feb 20 Python
基于python if 判断选择结构的实例详解
May 06 Python
详解用pyecharts Geo实现动态数据热力图城市找不到问题解决
Jun 26 Python
PyQt5+Caffe+Opencv搭建人脸识别登录界面
Aug 28 Python
numpy创建单位矩阵和对角矩阵的实例
Nov 29 Python
Python实现钉钉订阅消息功能
Jan 14 Python
python opencv肤色检测的实现示例
Dec 21 Python
PyCharm+Miniconda3安装配置教程详解
Feb 16 Python
python中的socket实现ftp客户端和服务器收发文件及md5加密文件
Apr 01 #Python
基于python实现FTP文件上传与下载操作(ftp&sftp协议)
Apr 01 #Python
django model的update时auto_now不被更新的原因及解决方式
Apr 01 #Python
pyautogui自动化控制鼠标和键盘操作的步骤
Apr 01 #Python
详解Python中pyautogui库的最全使用方法
Apr 01 #Python
django的模型类管理器——数据库操作的封装详解
Apr 01 #Python
opencv中图像叠加/图像融合/按位操作的实现
Apr 01 #Python
You might like
main.php
2006/12/09 PHP
php多任务程序实例解析
2014/07/19 PHP
php ajax实现文件上传进度条
2016/03/29 PHP
PHP 实现浏览记录并按日期分组
2017/05/11 PHP
php安装扩展mysqli的实现步骤及报错解决办法
2017/09/23 PHP
深入聊聊Array的sort方法的使用技巧.详细点评protype.js中的sortBy方法
2007/04/12 Javascript
jquery autocomplete自动完成插件的的使用方法
2010/08/07 Javascript
html组件不可输入(只读)同时任何组件都有效
2013/04/01 Javascript
关于jquery中全局函数each使用介绍
2013/12/10 Javascript
JavaScript原生对象之String对象的属性和方法详解
2015/03/13 Javascript
3个可以改善用户体验的AngularJS指令介绍
2015/06/18 Javascript
Jquery easyui开启行编辑模式增删改操作
2016/01/14 Javascript
Javascript实现图片加载从模糊到清晰显示的方法
2016/06/21 Javascript
第五篇Bootstrap 排版
2016/06/21 Javascript
jQuery扩展+xml实现表单验证功能的方法
2016/12/25 Javascript
JS动态修改网页body的背景色实例代码
2017/10/07 Javascript
详解webpack 热更新优化
2018/09/13 Javascript
nodejs图片处理工具gm用法小结
2018/12/12 NodeJs
koa-router路由参数和前端路由的结合详解
2019/05/19 Javascript
React Native 混合开发多入口加载方式详解
2019/09/23 Javascript
VUE实现强制渲染,强制更新
2019/10/29 Javascript
vue移动端使用appClound拉起支付宝支付的实现方法
2019/11/21 Javascript
[02:05]DOTA2完美大师赛趣味视频之看我表演
2017/11/18 DOTA
python executemany的使用及注意事项
2017/03/13 Python
python对于requests的封装方法详解
2019/01/03 Python
详解使用python绘制混淆矩阵(confusion_matrix)
2019/07/14 Python
Django中ajax发送post请求 报403错误CSRF验证失败解决方案
2019/08/13 Python
Python 多进程、多线程效率对比
2020/11/19 Python
中国医药集团国药在线:国药网
2017/02/06 全球购物
索桥的故事教学反思
2014/02/06 职场文书
岗位聘任书范文
2014/03/29 职场文书
大学生助学金感谢信
2015/01/21 职场文书
村官个人总结范文
2015/03/03 职场文书
2015小学教育教学工作总结
2015/07/21 职场文书
CSS3实现三角形不断放大效果
2021/04/13 HTML / CSS
jquery插件实现悬浮的菜单
2021/04/24 jQuery