当前位置: 首页 > 科技观察

奇门武侠:如何实现代码热更新

时间:2023-03-14 08:29:29 科技观察

本文转载自微信公众号《小菜学编程》,作者fasionchan。转载本文请联系小彩雪编程公众号。在学习了Python虚拟机、函数机制和类机制之后,我们对Python程序执行过程的动态性有了很好的理解:在运行时,Python可以动态创建函数对象;在运行时,Python可以动态创建类对象;在运行时,Python可以修改函数对象并改变它的行为;在运行时,Python可以修改类对象并改变它的行为;在运行时,Python可以动态编译代码并添加到虚拟机中执行;有了这些特性,我们就可以实现程序运行时的动态代码更新,即代码热更新!对于一般的程序,更新代码的唯一方法就是重启。因此,具有热更新能力的Python可以实现不可思议的功能。怎么做?——让我们从猴子补丁开始。Monkeypatch猴子补丁(monkeypatch)大家应该都听说过,这是一种在运行时添加和修改代码而不修改源代码的技术。JSON序列化是一个很常见的操作,在Python中可以这样实现:importjsonjson.dumps(some_data)ujson是JSON序列化的另一种实现,纯C语言编写,比标准库中的json模块效率更高,并且用法是一样的:importujsonujson.dumps(some_data)那么,如果要把整个程序中的json操作都换成ujson,该怎么办呢?直接引用ujson是肯定不行的,因为程序中可能会引用第三方库,我们当然不想更改第三方代码也不容易。以flask框架实现的一个api为例,fromflaskimportFlask,jsonifyapp=Flask(__name__)@app.route('/')defsome_api():returnjsonify(some_data)jsonify函数用于响应json数据,调用标准库json模块JSON序列化数据,但是flask不是我们开发的。幸运的是,利用Python执行过程的动态特性,我们可以在运行时替换json模块的相关功能实现。接下来,我们编写patch_json函数来替换dumps和loads函数:json模块中的函数替换为ujson版本。就算后面从json模块导入,最终的版本也是ujson版本!需要特别注意的是json模块属性是在调用patch_json之前直接导入的,不会被patch_json控制:importjsonfromjsonimportdumpspatch_json()#执行json模块的原始版本,而不是ujson版本dumps(some_data)#executeujsonversionjson.dumps(some_data)所以很多应用monkeypatches的程序都需要在一开始就执行替换逻辑,以保证不会出现类似的现象。Monkeypatch的应用范围很广,一般用于改变类库实现或者在单元测试中进行mock。例如greenlet使用monkeypatches将阻塞库函数替换为非阻塞版本:importgevent.monkeygevent.monkey.patch_all()因为monkeypatches可能会影响代码的可读性,应用不当可能会出现一些奇怪的问题,所以不能虐待。其实除了monkeypatching,Python还提供了reload函数,用于重新加载模块。那么,我们应该如何使用reload函数呢?它有什么限制吗?