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实现简单的计时器功能函数
Mar 14 Python
进一步了解Python中的XML 工具
Apr 13 Python
Python使用os模块和fileinput模块来操作文件目录
Jan 19 Python
Python实现快速排序和插入排序算法及自定义排序的示例
Feb 16 Python
使用Python的Django和layim实现即时通讯的方法
May 25 Python
图文详解Django使用Pycharm连接MySQL数据库
Aug 09 Python
Matplotlib使用字符串代替变量绘制散点图的方法
Feb 17 Python
PyCharm无法识别PyQt5的2种解决方法,ModuleNotFoundError: No module named 'pyqt5'
Feb 17 Python
如何在Python对Excel进行读取
Jun 04 Python
Python实现封装打包自己写的代码,被python import
Jul 12 Python
python爬取2021猫眼票房字体加密实例
Feb 19 Python
Python MNIST手写体识别详解与试练
Nov 07 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
Wordpress php 分页代码
2009/10/21 PHP
Thinkphp模板中使用自定义函数的方法
2012/09/23 PHP
php实现简单爬虫的开发
2016/03/28 PHP
CakePHP框架Model关联对象用法分析
2017/08/04 PHP
PHP实现的mysql操作类【MySQL与MySQLi方式】
2017/10/07 PHP
PHP空值检测函数与方法汇总
2017/11/19 PHP
PHP解密支付宝小程序的加密数据、手机号的示例代码
2021/02/26 PHP
ie和firefox不兼容的解决方法集合
2009/04/28 Javascript
jquery 图片Silhouette Fadeins渐显效果
2010/02/07 Javascript
js实现的日期操作类DateTime函数代码
2010/03/16 Javascript
简略的前端架构心得&&基于editor为例子的编码小技巧
2010/11/25 Javascript
javascript代码编写需要注意的7个小细节小结
2011/09/21 Javascript
js控制不同的时间段显示不同的css样式的实例代码
2013/11/04 Javascript
JSON 必知必会 观后记
2016/10/27 Javascript
JS按条件 serialize() 对应标签的使用方法
2017/07/24 Javascript
在Vue中使用echarts的方法
2018/02/05 Javascript
vue获取当前激活路由的方法
2018/03/17 Javascript
Vue基础学习之项目整合及优化
2019/06/02 Javascript
webpack常用配置总览(小结)
2019/11/18 Javascript
Python中关键字nonlocal和global的声明与解析
2017/03/12 Python
python实现外卖信息管理系统
2018/01/11 Python
Win10下python3.5和python2.7环境变量配置教程
2018/09/18 Python
Django中数据库的数据关系:一对一,一对多,多对多
2018/10/21 Python
django基于cors解决跨域请求问题详解
2019/08/06 Python
python实现一个函数版的名片管理系统过程解析
2019/08/27 Python
python实现加密的方式总结
2020/01/19 Python
python输出第n个默尼森数的实现示例
2020/03/08 Python
详解HTML5中rel属性的prefetch预加载功能使用
2016/05/06 HTML / CSS
Expedia泰国:预订机票、酒店和旅游包(航班+酒店)
2016/09/27 全球购物
表决心的诗句大全
2014/03/11 职场文书
酒后驾车标语
2014/06/30 职场文书
小学生关于梦想的演讲稿
2014/08/22 职场文书
领导党的群众路线教育实践活动个人对照检查材料
2014/09/23 职场文书
谢师宴邀请函
2015/02/02 职场文书
B站评分公认最好看的动漫,你的名字评分9.9,第六备受喜欢
2022/03/18 日漫
Spring Boot实现文件上传下载
2022/08/14 Java/Android