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)就是小编分享给大家的全部内容了,希望能给大家一个参考。
- Selenium+python自动化22-发送各种类型附件邮件
- Selenium2+python自动化38-显示等待(WebDriverWait)
- 逆元(个人模版)
- Selenium2+python自动化37-爬页面源码(page_source)
- ex_gcd(个人模版)
- Selenium2+python自动化36-判断元素存在
- Java A+B(个人模版)
- TensorFlow实战:SoftMax手写体MNIST识别(Python完整源码)
- set排序(个人模版)
- TSP(个人模版)
- 树的重心(个人模版)
- Selenium2+python自动化35-获取元素属性
- 2016广东工业大学新生杯决赛网络同步赛暨全国新生邀请赛 题解&源码
- 深入浅出MongoDB复制
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 仅2M!免费软件又一次干掉了付费版
- python爬虫学习 爬取幽默笑话网站
- 如何用Python快速优雅的批量修改Word文档样式?
- 为什么MySQL不推荐使用uuid或者雪花id作为主键?
- 用Python打造一款文件搜索工具,所有功能自己定义!
- 用Python绘制诱人的桑基图,一眼看透熬夜和狗粮的秘密
- magento换域名和服务器要怎么操作
- 从零搭建SpringBoot+MyBatis+MySQL工程
- 从零搭建SpringBoot+MyBatis+MySQL
- Typescript 使用日志(干货)
- 22+ 高频实用的 JavaScript 片段 (2020年)
- VSCode 的这些黑科技 99% 的人都不知道
- YOLOv4 中的 Mish 激活函数
- pdbsplit将多个chain的pdb文件分割
- rk3399 wifi和eth0共存 调试