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的Flask开发框架简单上手笔记
Nov 16 Python
Python正则表达式常用函数总结
Jun 24 Python
Python插件virtualenv搭建虚拟环境
Nov 20 Python
基于随机梯度下降的矩阵分解推荐算法(python)
Aug 31 Python
利用python、tensorflow、opencv、pyqt5实现人脸实时签到系统
Sep 25 Python
flask框架配置mysql数据库操作详解
Nov 29 Python
Pytorch之Variable的用法
Dec 31 Python
python递归函数求n的阶乘,优缺点及递归次数设置方式
Apr 02 Python
Python 爬虫性能相关总结
Aug 03 Python
Python字符串查找基本操作代码案例
Oct 27 Python
python3 re返回形式总结
Nov 20 Python
Python 恐龙跑跑小游戏实现流程
Feb 15 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
十大“创意”战术!
2020/03/04 星际争霸
PHP+SQL 注入攻击的技术实现以及预防办法
2011/01/27 PHP
Laravel 5框架学习之子视图和表单复用
2015/04/09 PHP
php自定义函数实现二维数组按指定key排序的方法
2016/09/29 PHP
用JQuery 判断某个属性是否存在hasAttr的解决方法
2013/04/26 Javascript
jQuery语法高亮插件支持各种程序源代码语法着色加亮
2013/04/27 Javascript
JS小功能(checkbox实现全选和全取消)实例代码
2013/11/28 Javascript
javascript获取form里的表单元素的示例代码
2014/02/14 Javascript
JS数组的常见用法实例
2015/02/10 Javascript
jQuery动画显示和隐藏效果实例演示(附demo源码下载)
2015/12/31 Javascript
利用AJAX实现WordPress中的文章列表及评论的分页功能
2016/05/17 Javascript
前端编码规范(3)JavaScript 开发规范
2017/01/21 Javascript
javascript 中的继承实例详解
2017/05/05 Javascript
详解webpack自动生成html页面
2017/06/29 Javascript
Vuejs 页面的区域化与组件封装的实现
2017/09/11 Javascript
nodejs多版本管理总结
2018/04/03 NodeJs
vue.js实现h5机器人聊天(测试版)
2020/07/16 Javascript
Python中pip安装非PyPI官网第三方库的方法
2015/06/02 Python
Python中的深拷贝和浅拷贝详解
2015/06/03 Python
Python的组合模式与责任链模式编程示例
2016/02/02 Python
Pythont特殊语法filter,map,reduce,apply使用方法
2016/02/27 Python
python3使用pyqt5制作一个超简单浏览器的实例
2017/10/19 Python
解决matplotlib库show()方法不显示图片的问题
2018/05/24 Python
在Python 字典中一键对应多个值的实例
2019/02/03 Python
对python 多线程中的守护线程与join的用法详解
2019/02/18 Python
Pytorch 实现计算分类器准确率(总分类及子分类)
2020/01/18 Python
Pandas之read_csv()读取文件跳过报错行的解决
2020/04/21 Python
Python 中由 yield 实现异步操作
2020/05/04 Python
把Anaconda中的环境导入到Pycharm里面的方法步骤
2020/10/30 Python
一文带你掌握Pyecharts地理数据可视化的方法
2021/02/06 Python
C# Debug和Testing相关面试题
2015/10/25 面试题
客户接待方案
2014/02/26 职场文书
医学生职业生涯规划书范文
2014/03/13 职场文书
草房子读书笔记
2015/06/29 职场文书
手把手教你使用TensorFlow2实现RNN
2021/07/15 Python
Win11筛选键导致键盘失灵怎么解决? Win11关闭筛选键的技巧
2022/04/08 数码科技