Python 函数运行时更新
Python 动态修改(运行时更新)
特性
- 实现函数运行时动态修改(开发的时候,非线上)
- 支持协程(tornado等)
- 兼容 python2, python3
安装
pip install realtimefunc
使用
from realtimefunc import realtimefunc
@coroutine
@realtimefunc
def test():
# function body
引言
后端服务的启动一般需要做相当多的准备工作, 导致启动的速度比较慢, 而在开发一项任务中很难做到一次性通过, 所以可能需要反复的重启服务, 这样相当的恼人。
另外, 接触一个 python 项目时, 当代码表示的逻辑难以理解, 或对代码表示的逻辑和实际效果有疑问的时候, 通常需要通过打印 log 的方式来理解, 或者通过 pdb 调试理解。
前者比较难以避免少打部分 log 而需要重启服务,后者功能强大但使用起来比较复杂,而且不能使当次修改,在下次调用及时生效(需要重启服务)。
备注:
很多web框架有自启动, 是通过检测项目文件的 mtime , 然后替换掉当前的服务进程,比如 tornado 就是用一个定时器定时检测项目文件, 实现 autostart。但这种重启的是整个服务,所以费时并不会减少。
这样很自然的就会想到, 如果可以随时改动开发的代码, 而不需要重启整个服务,就舒服多了。
实现目标:
实现一个装饰器, 被装饰的函数任意修改,无需重启服务,再次调用时及时生效。
实现思路:
实现一个装饰器, 被装饰函数实际不会被调用, 只提供入口(被装饰函数)。 装饰器通过入口提供的信息, 找到入口定义源代码,获取最新的代码, 定义成一个当前作用域的内存函数。实际运行的则是这个内存函数。最新代码 realtimefunc
"""A decorator is used to update a function at runtime."""
from __future__ import print_function
import sys
import os
import re
import ast
import linecache
import functools
import traceback
from inspect import getfile, isclass, findsource, getblock
__all__ = ["realtimefunc"]
Decorator = "@realtimefunc"
PY3 = sys.version_info >= (3,)
# The cache
# record, used to record functions decorated by realtimefunc,
# is a dict {filename:{func1, func2}
# refresh, used to to mark functions which source file stat has changed,
# is also a dict {filename:{func1, func2}}
# Note: the filename may be repeated but it doesn't matter.
record = {}
refresh = {}
def _exec(code, filepath, firstlineno, glob, loc=None):
astNode = ast.parse(code)
astNode = ast.increment_lineno(astNode, firstlineno)
code = compile(astNode, filepath, 'exec')
exec(code, glob, loc)
def _findclass(func):
cls = sys.modules.get(func.__module__)
if cls is None:
return None
for name in func.__qualname__.split('.')[:-1]:
cls = getattr(cls, name)
if not isclass(cls):
return None
return cls
def get_qualname(func):
'''return qualname by look through the call stack.'''
qualname = []
stacks = traceback.extract_stack(f=None)
begin_flag = False
for stack in stacks[::-1]:
if stack[3].strip() == Decorator:
qualname.append(func.__name__)
begin_flag = True
if stack[2] == '<module>':
break
if begin_flag:
qualname.append(stack[2])
return '.'.join(qualname[::-1])
def get_func_real_firstlineno(func):
start_lineno = 0
lines = linecache.getlines(func.__code__.co_filename)
cls = _findclass(func)
if cls:
lines, lnum = findsource(cls)
lines = getblock(lines[lnum:])
start_lineno = lnum
# referenced from inspect _findclass
pat = re.compile(r'^(\s*)def\s*' + func.__name__ + r'\b')
candidates = []
for i in range(len(lines)):
match = pat.match(lines[i])
if match:
# if it's at toplevel, it's already the best one
if lines[i][0] == 'd':
return (i + start_lineno)
# else add whitespace to candidate list
candidates.append((match.group(1), i))
if candidates:
# this will sort by whitespace, and by line number,
# less whitespace first
candidates.sort()
return start_lineno + candidates[0][1]
else:
raise OSError('could not find function definition')
def get_source_code(func, func_runtime_name, firstlineno):
lines = linecache.getlines(func.__code__.co_filename)
code_lines = getblock(lines[firstlineno:])
# repalce function name
code_lines[0] = code_lines[0].replace(func.__name__, func_runtime_name, 1)
i_indent = code_lines[0].index("def")
# code indentation
code_lines = [line[i_indent:] for line in code_lines]
code = ''.join(code_lines)
return code
def check_file_stat(filename):
entry = linecache.cache.get(filename, None)
change = False
if not entry:
change = True
else:
size, mtime, _, fullname = entry
try:
stat = os.stat(fullname)
except OSError:
change = True
del linecache.cache[filename]
if size != stat.st_size or mtime != stat.st_mtime:
change = True
del linecache.cache[filename]
if change:
global refresh
for f in record[filename]:
refresh.setdefault(filename, set()).add(f)
def realtimefunc(func):
# python2 need set __qualname__ by hand
if not PY3:
func.__qualname__ = get_qualname(func)
func_real_name = func.__qualname__.replace('.', '_') + '_realfunc'
filename = getfile(func)
filepath = os.path.abspath(filename)
global record, refresh
record.setdefault(filename, set()).add(func)
refresh.setdefault(filename, set()).add(func)
@functools.wraps(func)
def wrapper(*args, **kwargs):
glob = func.__globals__
check_file_stat(filename)
if func in refresh[filename]:
firstlineno = get_func_real_firstlineno(func)
code_str = get_source_code(func, func_real_name, firstlineno)
_exec(code_str, filepath, firstlineno, glob)
refresh[filename].remove(func)
func_realtime = glob[func_real_name]
return func_realtime(*args, **kwargs)
return wrapper
效果
实现开发运行时修改函数, 可以很方便的查看和修改被装饰函数相关的数据,以及构造简单测试数据和进行简单的分支测试。
TODO
- 支持 log 打印, log 打印时, 显示的文件为 <string>, 行号也是相对的。 已实现
- 内存函数可以优化为只有在改动的时候重新定义,也就是可以做缓存。 - 已实现
Python 函数运行时更新的更多相关文章
- Python函数汇总(陆续更新中...)
range的用法 函数原型:range(start, end, scan): 参数含义: start:计数从start开始.默认是从0开始.例如range(5)等价于range(0, 5); end: ...
- python在运行时终止执行 sys.exit
使用sys.exit 或者exit,quit均可以退出执行 # 程序执行中,需要时停止执行 import sys if __name__ == '__main__': for ii in range( ...
- linux下实现在程序运行时的函数替换(热补丁)
声明:以下的代码成果,是参考了网上的injso技术,在本文的最后会给出地址,同时非常感谢injso技术原作者的分享. 但是injso文章中的代码存在一些问题,所以后面出现的代码是经过作者修改和检测的. ...
- linux下实现在程序运行时的函数替换(热补丁)【转】
转自:http://www.cnblogs.com/leo0000/p/5632642.html 声明:以下的代码成果,是参考了网上的injso技术,在本文的最后会给出地址,同时非常感谢injso技术 ...
- Python函数信息
Python函数func的信息可以通过func.func_*和func.func_code来获取 一.先看看它们的应用吧: 1.获取原函数名称: 1 >>> def yes():pa ...
- python 在调用时计算默认值
大家都知道python的默认值是在函数定义时计算出来的, 也就是说默认值只会计算一次, 之后函数调用时, 如果参数没有给出,同一个值会赋值给变量, 这会导致, 如果我们想要一个list默认值, 新手通 ...
- python函数与装饰器
一.名字空间与作用域 1.名字空间 名字空间:赋值语句创建了约束,用来存储约束的dict被称为名字空间 赋值语句的行为:1.分别在堆和栈中创建obj与name ...
- Python函数进阶:闭包、装饰器、生成器、协程
返回目录 本篇索引 (1)闭包 (2)装饰器 (3)生成器 (4)协程 (1)闭包 闭包(closure)是很多现代编程语言都有的特点,像C++.Java.JavaScript等都实现或部分实现了闭包 ...
- 日志系统实战(二)-AOP动态获取运行时数据
介绍 这篇距上一篇已经拖3个月之久了,批评自己下. 通过上篇介绍了解如何利用mono反射代码,可以拿出编译好的静态数据.例如方法参数信息之类的. 但实际情况是往往需要的是运行时的数据,就是用户输入等外 ...
随机推荐
- c++迭代递归实现汉诺塔(5种迭代方法满足你)
#include <iostream> //从A到C using namespace std; int n; void ready() { cout << "请输入汉 ...
- 【转】c# delegate
源地址:https://www.cnblogs.com/lcawen/p/6645358.html
- 【bzoj2500】幸福的道路 树形dp+单调队列
Description 小T与小L终于决定走在一起,他们不想浪费在一起的每一分每一秒,所以他们决定每天早上一同晨练来享受在一起的时光. 他们画出了晨练路线的草图,眼尖的小T发现可以用树来描绘这个草图. ...
- cenos安装memcache
注意事项: 1 安装时注意权限问题 sudo 2 需先启动memcache服务 php才能测试 Memcached是高性能的,分布式的内存对象缓存系统,用于在动态应用中减少数据库负载,提升访问速度 ...
- linux操作之文本编辑器
1.文本编辑器的作用 编辑和修改系统中的那些以文本形式存在的文件(特别是各种配置文件),也可以用于 编写程序代码 2.linux下的常见编辑器 nano.Emacs.gedit.vim等 3.vim三 ...
- SDUT OJ 数据结构实验之二叉树八:(中序后序)求二叉树的深度
数据结构实验之二叉树八:(中序后序)求二叉树的深度 Time Limit: 1000 ms Memory Limit: 65536 KiB Submit Statistic Discuss Probl ...
- windows下Idea结合maven开发spark和本地调试
本人的开发环境: 1.虚拟机centos 6.5 2.jdk 1.8 3.spark2.2.0 4.scala 2.11.8 5.maven 3.5.2 在开发和搭环境时必须注意版本兼容的问题 ...
- jeesite模块解析,功能实现
做为十分优秀的开源框架,JeeSite拥有着很多实用性的东西. 默认根路径跳转 定义了无Controller的path<->view直接映射 <mvc:view-controller ...
- Spring学习笔记(二)——Spring相关配置&属性注入&Junit整合
一.Spring的相关配置 1.1 Bean元素 class属性:被管理对象的完整类名 name属性:给Bean起个名字,能重复,能使用特殊字符.后来属性 id属性:给Bean起个名字,不能重复,不能 ...
- Linux 未安装vi如何编辑文件
sed -i "s/搜索内容/替换内容/g" 文件名