Python 函数热更(运行时更新)
标签(空格分隔): python
特性
实现函数运行时修改(开发环境!!!, 非线上热更!!!)
支持协程函数(tornado)
用法
from /path/to/realtimefunc import realtimefunc
@coroutine
@realtimefunc
def test():
# function body
故事
说到热更, 很容易就会联想到线上产品的热更。 有前端, 也有后台。不过在这里提及到的是 python 后台开发时候的热更。
开发也需要热更 ??
开发也有人权的啊, 什么! 没有? 那就自己折腾个。
后端服务的启动一般需要做相当多的准备工作, 导致启动的速度比较慢。开发时用的服务器一般比不上线上的,启动速度就更引入注目了。
当你作为一个小白刚接触到一个 python 后端项目, 需要在上面做开发时, 你可能会遇到两个情况。
1. 去理解一个功能, 数据存储的结构在理解占很大一部分,对 python 这种动态数据结构, 通过代码, 很难清晰看到一个功能(相关dict,list, set等)定义的数据结构, 或者一些全局变量的具体结构以及内容。一些固定的也许可以直接通过 db 查看数据结构, 但一些内存的中的数据,就难以顾及了。
2. 实现一个功能写了一大段代码, 这大段代码中隐藏 bug 团伙,python 是运行时检测,也就是代码运行到具体语句才会报错, 这样报错之后的 bug 君依然得以隐藏,如果服务启动需要5分钟,bug 团伙规模达到 6 个以上,小半个小时就没了。而这些 bug 可能只是简单 key error, 真是想想都要崩溃。
备注:
很多web框架有自启动, 是通过检测项目文件的 mtime , 然后替换掉当前的服务进程,比如 tornado 就是用一个定时器定时检测项目文件, 实现 autostart。
这样很自然的就会想到, 如果可以随时改动开发的代码, 而不需要重启整个服务,岂不是很爽。
实现目标:
实现一个装饰器, 被装饰的函数任意修改,无需重启服务,新请求立即生效。
实现思路:
在被装饰函数调用时, 利用 inspect.getsource 从 .py 文件获取该函数具体代码, 通过 exec 重新定义和命名该函数, 使得与函数代码的修改能在下次调用中生效。最新代码地址 realtimefunc
# -*- coding: iso-8859-1 -*-
import sys
import linecache
import re
from inspect import getsource, getfile
# A decorator is used to update a function at runtime.
DecoratorName = 'realtimefunc'
suffix = '_runtime'
PY3 = sys.version_info >= (3,)
if PY3:
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
if isinstance(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__)
for i, line in enumerate(code_lines):
if "@"+DecoratorName in line:
i_decorator = i
if func_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:] for line in code_lines]
code = split.join(code_lines)
return code
def realtimefunc(func):
def wrapper(*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.
return func.__globals__[func.__name__+suffix](*args, **kwargs)
return wrapper
效果
实现开发运行时修改函数, 可以很方便的查看和修改被装饰函数相关的数据,以及构造简单测试数据和进行简单的分支测试。
注意
对于协成函数, 比如如果使用低版本的 tornado (比如 4.1) 请将 return 改为 yield, 位置在代码中有注释
本文适用小白, 大神绕道, 小白自娱!!!