Python 函数热更 (开发时)
Python 函数热更(运行时更新)
标签(空格分隔): python
特性
实现函数运行时修改(开发环境!!!, 非线上热更!!!)
支持协程函数(tornado)
用法from/path/to/realtimefuncimportrealtimefunc
@coroutine
@realtimefunc
deftest():
# function body
故事
说到热更, 很容易就会联想到线上产品的热更. 有前端, 也有后台. 不过在这里提及到的是 python 后台开发时候的热更.
开发也需要热更 ??
开发也有人权的啊, 什么! 没有? 那就自己折腾个.
后端服务的启动一般需要做相当多的准备工作, 导致启动的速度比较慢. 开发时用的服务器一般比不上线上的, 启动速度就更引入注目了.
当你作为一个小白刚接触到一个 python 后端项目, 需要在上面做开发时, 你可能会遇到两个情况.
去理解一个功能, 数据存储的结构在理解占很大一部分, 对 python 这种动态数据结构, 通过代码, 很难清晰看到一个功能 (相关 dict,list, set 等) 定义的数据结构, 或者一些全局变量的具体结构以及内容. 一些固定的也许可以直接通过 db 查看数据结构, 但一些内存的中的数据, 就难以顾及了.
实现一个功能写了一大段代码, 这大段代码中隐藏 bug 团伙, python 是运行时检测, 也就是代码运行到具体语句才会报错, 这样报错之后的 bug 君依然得以隐藏, 如果服务启动需要 5 分钟, bug 团伙规模达到 6 个以上, 小半个小时就没了. 而这些 bug 可能只是简单 key error, 真是想想都要崩溃.
备注:
很多 web 框架有自启动, 是通过检测项目文件的 mtime , 然后替换掉当前的服务进程, 比如 tornado 就是用一个定时器定时检测项目文件, 实现 autostart.
这样很自然的就会想到, 如果可以随时改动开发的代码, 而不需要重启整个服务, 岂不是很爽.
实现目标:
实现一个装饰器, 被装饰的函数任意修改, 无需重启服务, 新请求立即生效.
实现思路:
在被装饰函数调用时, 利用 inspect.getsource 从 .py 文件获取该函数具体代码, 通过 exec 重新定义和命名该函数, 使得与函数代码的修改能在下次调用中生效. 最新代码地址 https://github.com/Graywd/realtimefunc# -*- coding: iso-8859-1 -*-
importsys
importlinecache
importre
frominspectimportgetsource,getfile
# A decorator is used to update a function at runtime.
DecoratorName='realtimefunc'
suffix='_runtime'
PY3=sys.version_info>=(3,)
ifPY3:
basestring_type=str
else:
basestring_type=basestring# noqa
def_exec_in(code,glob,loc=None):
# type: (Any, Dict[str, Any], Optional[Mapping[str, Any]]) -> Any
ifisinstance(code,basestring_type):
# exec(string) inherits the caller's future imports; compile
# the string first to prevent that.
code=compile(code,'','exec',dont_inherit=True)
exec(code,glob,loc)
def_handle_real_time_func_code(func,split='\n'):
code=getsource(func)
i_indent=0
i_decorator=0
code_lines=code.split(split)
func_pat=re.compile(r'^\s*def\s+'+func.__name__)
fori,lineinenumerate(code_lines):
if"@"+DecoratorNameinline:
i_decorator=i
iffunc_pat.match(line):
i_indent=line.index("def")
code_lines[i]=code_lines[i].replace(func.func_name,func.func_name+suffix,1)
break
# rm realtimefunc decorator
code_lines.pop(i_decorator)
# code indentation
code_lines=[line[i_indent:]forlineincode_lines]
code=split.join(code_lines)
returncode
defrealtimefunc(func):
defwrapper(*args,**kwargs):
filename=getfile(func)
# inspect use linecache to do file cache, so do checkcache first
linecache.checkcache(filename)
code_str=_handle_real_time_func_code(func)
_exec_in(code_str,func.__globals__,func.__globals__)
# A return expected when is work, if not yield instead.
returnfunc.__globals__[func.__name__+suffix](*args,**kwargs)
returnwrapper
效果
实现开发运行时修改函数, 可以很方便的查看和修改被装饰函数相关的数据, 以及构造简单测试数据和进行简单的分支测试.
注意
对于协成函数, 比如如果使用低版本的 tornado (比如 4.1) 请将 return 改为 yield, 位置在代码中有注释
inspect 中有用到 linecache 做缓存, 在获取函数源码的时候需要检查缓存
本文适用小白, 大神绕道, 小白自娱!!!
来源: https://www.cnblogs.com/nowg/p/9517478.html