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结合shell查询google关键词排名的实现代码
Feb 27 Python
Python设计模式之门面模式简单示例
Jan 09 Python
python正则表达式爬取猫眼电影top100
Feb 24 Python
Python数据处理numpy.median的实例讲解
Apr 02 Python
Python简单爬虫导出CSV文件的实例讲解
Jul 06 Python
tensorflow实现图像的裁剪和填充方法
Jul 27 Python
Python 用turtle实现用正方形画圆的例子
Nov 21 Python
Python 支持向量机分类器的实现
Jan 15 Python
matlab、python中矩阵的互相导入导出方式
Jun 01 Python
python 安装移动复制第三方库操作
Jul 13 Python
详解pytorch中squeeze()和unsqueeze()函数介绍
Sep 03 Python
python 实现的截屏工具
May 08 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
php socket实现的聊天室代码分享
2014/08/16 PHP
php中注册器模式类用法实例分析
2015/11/03 PHP
在JS中最常看到切最容易迷惑的语法(转)
2010/10/29 Javascript
三级下拉菜单的js实现代码
2011/05/23 Javascript
Jquery 的扩展方法总结
2011/10/01 Javascript
js获取select标签的值且兼容IE与firefox
2013/12/30 Javascript
jQuery实现带有洗牌效果的动画分页实例
2015/08/31 Javascript
Jquery循环截取字符串的方法(多出的字符串处理成"...")
2016/11/28 Javascript
js的三种继承方式详解
2017/01/21 Javascript
Bootstrap Table中的多选框删除功能
2018/07/15 Javascript
详解如何在vue项目中使用layui框架及采坑
2019/05/05 Javascript
layui 表单标签的校验方法
2019/09/04 Javascript
ES6学习笔记之字符串、数组、对象、函数新增知识点实例分析
2020/01/22 Javascript
JavaScript数组排序功能简单实现
2020/05/14 Javascript
深入解析微信小程序开发中遇到的几个小问题
2020/07/11 Javascript
[01:14:10]2014 DOTA2国际邀请赛中国区预选赛 SPD-GAMING VS Orenda
2014/05/22 DOTA
使用python编写android截屏脚本双击运行即可
2014/07/21 Python
Python解决N阶台阶走法问题的方法分析
2017/12/28 Python
Python 实现自动获取种子磁力链接方式
2020/01/16 Python
美国儿童运动鞋和服装零售商:Kids Foot Locker
2017/08/05 全球购物
美国礼品卡商城: Gift Card Mall
2017/08/25 全球购物
Omio法国:全欧洲低价大巴、火车和航班搜索和比价
2017/11/13 全球购物
Supersmart英国:欧洲市场首批食品补充剂供应商之一
2018/05/05 全球购物
美国踏板车和轻便摩托车销售网站:Mega Motor Madness
2020/02/26 全球购物
为什么在使用动态 SQL 语句时必须为低层数据库对象授予权限
2012/12/13 面试题
应届毕业生就业自荐信
2013/10/26 职场文书
学生思想表现的评语
2014/01/30 职场文书
遗体告别仪式主持词
2014/03/20 职场文书
拾金不昧表扬信怎么写
2015/05/04 职场文书
老公写给老婆的检讨书
2015/05/06 职场文书
行政处罚告知书
2015/07/01 职场文书
2016消防宣传标语口号
2015/12/26 职场文书
Nginx 负载均衡是什么以及该如何配置
2021/03/31 Servers
JavaScript嵌入百度地图API的最详细方法
2021/04/16 Javascript
vue实现省市区联动 element-china-area-data插件
2022/04/22 Vue.js
CSS使用SVG实现动态分布的圆环发散路径动画
2022/12/24 HTML / CSS