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反射代码,可以拿出编译好的静态数据.例如方法参数信息之类的. 但实际情况是往往需要的是运行时的数据,就是用户输入等外 ...
随机推荐
- 解决在cmder中bash(WSL)上下箭头不能使用问题
有三种解决方式,第一种方式最简单实用 安装新版本wslbridge 这个解决方法最简单,最实用,下载第三方wslbridge,安装即可使用. 这时再进入cmder,运行bash.exe,可以发现上下左 ...
- 【kudu pk parquet】runtime filter实践
已经有好一阵子没有写博文了,今天给大家带来一篇最近一段时间开发相关的文章:在impala和kudu上支持runtime filter. 大家搜索下实践者社区,可以发现前面已经有好几位同学写了这个主题的 ...
- ubuntu14.04,安装Git(源代码管理工具)
在shell中执行:sudo apt-get install git-core
- UIButton的几种触发方式
1.说明 说明:由于是在"iOS 模拟器"中测试的,所以不能用手指,只能用鼠标. 1)UIControlEventTouchDown 指鼠标左键按下(注:只是"按下&qu ...
- 预定义宏,C语言预定义的宏详解
1.预定义宏 对于预定义宏,相信大家并不陌生.为了方便处理一些有用的信息,预处理器定义了一些预处理标识符,也就是预定义宏.预定义宏的名称都是以"__"(两条下划线)开头和结尾的,如 ...
- react.js学习之路五
最近没时间写博客,但是我一直在学习react,我发现react是一个巨大的坑,而且永远填不完的坑 关于字符串的拼接: 在react中,字符串的拼接不允许出现双引号“” ,只能使用单引号' ',例如这样 ...
- HDU6308-2018ACM暑假多校联合训练1011-Time Zone
题目大意就是给你UTC-8时区的时间 让你求对应时区的时间 哇 这个题 看似简单,但是一开始怎么都过不了啊 同学用自己写的read过了,后来看了一下各位大佬说改成分钟随便过,就随便过了 Problem ...
- 1. C/C++项目一
需求: 使用C语言封装string 字符串,实现字符串的增.删.改.查等API函数. 要求: 不能使用 string 库函数,所有库函数必须自己手动实现. [项目实现] myString.h 代码如下 ...
- 使用原生实现jquery中的css方法
由于jquery放在mobile页面上,有时候还是显得有点大,所以今天尝试使用原生来开发,但是习惯了jquery之后,转用原生开发之后,发现原生中,找不到可以替代jquery的css方法,于是对原生的 ...
- 学习markdown语法,易读易写,放2个教程地址
http://wowubuntu.com/markdown/basic.html http://wowubuntu.com/markdown/basic.html