Python自动重新加载模块详解(autoreload module)
作者:promissing 发布时间:2023-09-19 04:36:12
守护进程模式
使用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
来源:https://www.cnblogs.com/wenjunxiao/p/4093377.html


猜你喜欢
- PHP程序员玩转Linux系列文章:1.PHP程序员玩转Linux系列-怎么安装使用CentOS2.PHP程序员玩转Linux系列-lnmp
- 问题场景有时候,在编写一段http接口请求程序时,发现代码中的header头和请求体中都是原网页中一样,但是,在实际请求时,接口却返回404
- 下面这段代码,不会替换ld<sad中间的<,所以内容仍然是正常的 <% function nohtml(str) dim
- [摘要]了解如何充分利用SQL Server 2000的全文搜索功能。本文包含有关实现最大吞吐量和最佳性能的几点提示和技
- 前言为了机器人在寻路的过程中避障并且找到最短距离,我们需要使用一些算法进行路径规划(Path Planning),常用的算法有Djikstr
- 下面的asp函数实现了对站点的所有缓存Application的清理,释放!Sub RemoveAllCache() D
- 利用这个小游戏可以学习一下ython3.3中tkinter的使用方法# -*- coding: utf-8 -*-import tkinte
- Hello, 大家好,又是我~ 大家有看过font set和一些要注意的基本问题以及通用字体族两篇文章后,应该对字体的基本有了一些了解。现
- Labelme简要介绍通过labelme对图进行标注后,得到的是json文件,而Yolov5对数据进行模型构建的时候,读取需要的是txt格式
- mysql是我们项目中非常常用的数据型数据库。但是因为我们需要在数据库保存中文字符,所以经常遇到数据库乱码情况。下面就来介绍一下如何彻底解决
- 一、自定义分页1、基础版自定义分页data = []for i in range(1, 302): tmp = {"i
- 前言本文的文字及图片来源于网络,仅供学习、交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理。闲来无事听听歌,听
- 环境:Python3.7依赖库:import datetimeimport randomimport requestsimport hash
- 使用命令行登录MySQL报错1045 Access denied for user ‘root’@&
- python 的虚拟环境可以为一个 python 项目提供独立的解释环境、依赖包等资源,既能够很好的隔离不同项目使用不同 python 版本
- 本文实例讲述了python写xml文件的操作的方法,分享给大家供大家参考。具体方法如下:要生成的xml文件格式如下:<?xml ver
- 一、前言return一直中,每中语言中其没没有很大差别,就不多说了。(shell语言return的是退出状态,可能差别是比较大的)最早看到y
- element-ui中el-form自定义验证需求在输入项目名称后,调用后端接口isNameOnly,若已存在,则效果如下图:1.先设置校验
- 引言通过一张照片居然发现女友在宿舍里没去上课!强大的照片位置信息获取,快来一起学习吧!一、exifread函数库要怎样获得拍摄图片的GPS呢
- Matplotlib官网 如果想了解更多可查看官网。import numpy as np import matplotlib.py