Python 函数热更(运行时更新)
标签(空格分隔): python
特性
实现函数运行时修改(开发环境!!!, 非线上热更!!!)
支持协程函数(tornado)
用法
- from /path/to/realtimefunc import realtimefunc
- @coroutine
- @realtimefunc
- def test():
- # 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 -*-
- 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, '<string>', '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, 位置在代码中有注释
inspect 中有用到 linecache 做缓存, 在获取函数源码的时候需要检查缓存
本文适用小白, 大神绕道, 小白自娱!!!
来源: https://www.cnblogs.com/nowg/p/9517478.html