python 热更新代码_Python代码热更新实现

Python代码热更新可以找到实际可用的实现,比如IPython的autoreload.py、PyDev的pydevd_realod.py。

但如果换成自己实现,怎么从头来考虑这个问题呢?

什么是热更新

简单来说,热更新就是在进程不重启的情况下,让其加载修改后的程序代码,且能按照预期正确执行。在实际开发中,热更新的最主要用途有,

开发期,提升开发效率,让代码改动立等可见,避免频繁重启

运维期,在服务端不断线情况下修复紧急bug

对于bug修复来说,服务端如果不保持状态,那么热更新的必要性不那么大,但如果服务端持有复杂状态,那么热更新就会是一个比较合适的选择。

热更新的要点

Python代码是以module进行组织的,代码热更新就是module的热更新。

Python提供的内置函数reload用于重新加载模块,然而直接使用reload并不能够解决热更新这一问题。热更新的要点在于需要让已经创建的对象能够执行更新以后的代码。这也是autoreload.py、pyded_reload.py中大部分代码实现的目的所在。

更新操作拆解

更新普通函数

整个热更新逻辑中,函数的更新是最为重要的,因为函数是具体逻辑的执行单元。参照上述实现,定义函数更新的实现如下,

def update_function(old_func, new_func):

old_func.__doc__ = new_func.__doc__

old_func.__dict__ = new_func.__dict__

old_func.__defaults__ = new_func.__defaults__

old_func.__code__ = new_func.__code__

上述函数可以用简单样例进行验证,

def old_foo():

return 'old_foo'

def new_foo():

return 'new_foo'

class ReloadTest(unittest.TestCase):

def test_update_function(self):

self.assertEqual('old_foo', old_foo())

update_function(old_foo, new_foo)

self.assertEqual('new_foo', old_foo())

更新decorator修饰的函数

目前的实现可以通过上面的测试用例,与pydevd_reload.py中的_update_function是一致的,但根据pydevd_reload.py的注释,

Functions and methods using decorators (other than classmethod and staticmethod) are not handled correctly.

也就是说这样的实现是不支持decorator的,因此扩充下用例,

def decorator(func):

def _(*args, **kwargs):

return func(*args, **kwargs)

return _

@decorator

def old_foo_with_decorator():

return 'old_foo'

@decorator

def new_foo_with_decorator():

return 'new_foo'

class ReloadTest(unittest.TestCase):

def test_update_function_with_decorator1(self):

self.assertEqual('old_foo', old_foo_with_decorator())

update_function(old_foo_with_decorator, new_foo_with_decorator)

self.assertEqual('new_foo', old_foo_with_decorator())

def test_update_function_with_decorator2(self):

self.assertEqual('old_foo', old_foo())

update_function(old_foo, old_foo_with_decorator)

self.assertEqual('new_foo', old_foo())

上述两个case都会失败。先来解决第一个case失败的情况,当需要更新的函数都被decorator修饰的时候,update_function并没有发生作用,这种情况可以通过递归进行处理,修改update_function如下,

def both_instance_of(first, second, klass):

return isinstance(first, klass) and isinstance(second, klass)

def update_function(old_func, new_func):

old_func.__code__ = new_func.__code__

old_func.__defaults__ = new_func.__defaults__

old_func.__doc__ = new_func.__doc__

old_func.__dict__ = new_func.__dict__

if not old_func.__closure__ or not new_func.__closure__:

return

for old_cell, new_cell in zip(old_func.__closure__, new_func.__closure__):

if not both_instance_of(old_cell.cell_contents, new_cell.cell_contents, types.FunctionType):

continue

update_function(old_cell.cell_contents, new_cell.cell_contents)

被decorator修饰的函数可以通过最终返回函数的free variable找到,因此可以递归更新函数的__closure__来进行处理。

第二个case会遇到如下异常,

ValueError: _() requires a code object with .. free vars, not ..

之所以会抛这个异常,是因为Python在funcobject.c的func_set_code函数中进行了强制检查,如果不修改Python源码的话,应该是绕不过去了。因此update_function需要稍作调整,这种情况下不进行更新,而不抛异常,

def update_function(old_func, new_func):

if not both_instance_of(old_func, new_func, types.FunctionType):

return

if len(old_func.__code__.co_freevars) != len(new_func.__code__.co_freevars):

return

old_func.__code__ = new_func.__code__

old_func.__defaults__ = new_func.__defaults__

old_func.__doc__ = new_func.__doc__

old_func.__dict__ = new_func.__dict__

if not old_func.__closure__ or not new_func.__closure__:

return

for old_cell, new_cell in zip(old_func.__closure__, new_func.__closure__):

if not both_instance_of(old_cell.cell_contents, new_cell.cell_contents, types.FunctionType):

continue

update_function(old_cell.cell_contents, new_cell.cell_contents)

更新类

在处理完函数更新之后就可以来实现类更新了,类更新涉及普通成员函数、类函数、静态函数、property的更新,同时需要对属性增删进行处理。

def update_class(old_class, new_class):

for name, new_attr in new_class.__dict__.items():

if name not in old_class.__dict__:

setattr(old_class, name, new_attr)

else:

old_attr = old_class.__dict__[name]

if both_instance_of(old_attr, new_attr, types.FunctionType):

update_function(old_attr, new_attr)

elif both_instance_of(old_attr, new_attr, staticmethod):

update_function(old_attr.__func__, new_attr.__func__)

elif both_instance_of(old_attr, new_attr, classmethod):

update_function(old_attr.__func__, new_attr.__func__)

elif both_instance_of(old_attr, new_attr, property):

update_function(old_attr.fdel, new_attr.fdel)

update_function(old_attr.fget, new_attr.fget)

update_function(old_attr.fset, new_attr.fset)

elif both_instance_of(old_attr, new_attr, (type, types.ClassType)):

update_class(old_attr, new_attr)

不过类上面的__slots__、__metaclass__如果发生了变化,也是无法正确更新的。

更新模块

模块上的更新也是类似类更新处理,这里只处理通常情况下模块内可能直接存在的类型,

def update_module(old_module, new_module):

for name, new_val in new_module.__dict__.iteritems():

if name not in old_module.__dict__:

setattr(old_module, name, new_val)

else:

old_val = old_module.__dict__[name]

if both_instance_of(old_val, new_val, types.FunctionType):

update_function(old_val, new_val)

elif both_instance_of(old_val, new_val, (type, types.ClassType)):

update_class(old_val, new_val)

定义回调接口

一路分析下来可以看到热更新不仅有不少限制,且有一些问题是没有进行处理的,

定义在模块或类上的属性没有进行处理

新增成员属性没有进行处理

无法在更新发生时执行某种操作

因此需要在合适的实际执行一些固定调用,好让上层逻辑能介入更新流程,进行特定处理来达成实际需求。

模块更新回调,可以在update_module的最后增加处理,约定回调的函数名称,

def update_module(old_module, new_module):

...

if hasattr(old_module, '_on_reload_module'):

old_module._on_reload_module()

类更新回调,在update_class最后进行处理,

def update_class(old_class, new_class):

...

if hasattr(old_class, '_on_reload_class'):

old_class._on_reload_class()

就像pydevd_reload.py里约定的__reload_update__这个hook函数一样,一路思考下来就可以理解为什么它需要定义这样的函数。

如果在__init__里面增加了属性定义,对于旧对象来说__init__是不会再次执行的,因此没有机会创建属性。所以一般想以热更新方式更新代码时需要注意避免这种情况,如果实在绕不开,那么有两种处理思路,一是在使用新属性的时候都使用getattr,作为临时的workaround,勉强能接受。另外一种则是在更新时找到之前创建的所有对象,主动给对象设置新属性的初始值。寻找类实例可以通过gc模块,

def update_class(old_class, new_class):

...

if hasattr(old_class, '_on_reload_instance'):

for x in gc.get_referrers(old_class):

if isinstance(x, old_class):

x._on_reload_instance()

总结

在实现上述函数之后,就有了代码热更新的执行逻辑了,找到需要更新的模块,然后调用update_module就可以了。不过是不是还有什么东西没写到呢?就是如何找到需要更新的模块。不过有点写不动了,之后再慢慢总结吧。

在遇到热更新这一问题的时候,先是各种搜索,当找到一些实现之后,阅读其代码并尝试理解。但这样未必弄得明白,这种情况下自己尝试一步步来实现,那么对问题可以有更深的认识,同时也更容易理解那些已有实现中的代码究竟为何要那样实现。

参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值