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反射代码,可以拿出编译好的静态数据.例如方法参数信息之类的. 但实际情况是往往需要的是运行时的数据,就是用户输入等外 ...
随机推荐
- SQL Server 常用函数总结
SQL去空格函数 1.ltrim(‘内容’)--去掉字符左边的空格 代码如下 declare @str varchar(100) set @str=' ADFADF' select @str sele ...
- Android学习笔记 - 开始
因为项目需求,要在Android上开发一个证件识别软件,项目时间 9/10- 9/30 工作内容: (1)修改证件识别库 (2)移植证件识别库至Android (3)开发一个Android应用程序 学 ...
- Unite Shanghai 2019全日程曝光(建议收藏)
https://mp.weixin.qq.com/s/KvAyXpDhqWROtTX1Ol3a4Q 5月10-12日,Unite Shanghai 2019即将在上海国际会议中心正式开幕.本次大会共设 ...
- Spring之BeanFactory与ApplicationConText
:BeanFactory基本的工厂解析,管理,实例化所有容器内的bean的接口,spring中所有解析配置文件的类都直接或者间接实现该接口ApplicationContext接口implements ...
- MySQL 学习笔记(三):完整性和触发器设计
(一)完整性设计 方法一.在设计表时定义约束 删除数据库school,建立新数据库school1 drop database school; create database school; use s ...
- 深度剖析MQTT协议的整个通信流程
http://www.elecfans.com/d/587483.html MQTT,目前物联网的最主要的协议,基本所有收费的云平台都是基于MQTT协议,比如机智云,和所有的开放云平台比如中国移动的o ...
- 流水的算法,铁打的损失函数/MLE
机器学习算法可以说是不少的,如果死记硬背的话,只能当时记得推导过程和步骤,过一段时间就又想不起来了,只能依稀记得一些影子.所以,应该找到算法的一些通用的方法来理解算法的思路以及推导过程. 我认为,最大 ...
- MySQL事务及事务隔离级别 锁机制
什么是事务? 当多个用户访问同一份数据时,一个用户在更改数据的过程中可能有其他用户同时发起更改请求,为保证数据库记录的更新从一个一致性状态更改为另一个一致性状态,这样的操作过程就是事务.事务具有的AC ...
- 推荐分享一个牛X的自定义PHP加密解密类
通俗点说,用它来进行加密,同一个字符串,每次进行加密,得出的结果都是不一样的,大大加强了数据安全性.同时还可设定加密后数据的有效期,简直牛掰了 #食用方法 将下面的第二份模块代码保存为 Mcrypt. ...
- ajax beforeSend中无效果
asnyc:false 与beforesend 同时使用 无效果