python热更新的随笔

现有的热更新比如IPython的autoreload.py、PyDev的pydevd_realod.py
但是这两个都有缺陷。
姑且认为有 old 和 new 这两个目录分别有着热更新前后的代码,old正在运行中
python的内置的reload做了什么
将指定模块的源码重新编译了一遍,并执行可执行的代码
相当于把xxx.XXX全部重新赋值了一遍
from xxx import XXX 是无效的
因为相当于给xxx.XXX了一个XXX的别名并引用,即使你import xxx之后reload(xxx)也不会生效
所以实例和一些对象,容器依然全部引用的旧的类,函数,变量,所以需要我们自己做替换
一个python模块中 可能有这么几种东西
①全局变量(实例)
②函数
③类
###函数的更新
函数的更新最为简单,只需要替换掉__doc__, __dict__, __defaults__, __code__即可

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__

但是这样是不支持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的办法

def both_instance_of(first, second, klass):
    return isinstance(first, klass) and isinstance(second, klass)

def update_function(old_func, new_func):
    # 这里两个检查是因为python在funcobj.c的func_set_code里面进行了强制检查
    # 也就是在执行old_func.__code__ = new_func.__code__的时候会报错
    # 所以在这里如果碰到前后有无装饰器的函数只能先跳过了
    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)

###类的更新
一个类可能有这些东西
①方法(包括普通方法,静态方法,类方法,属性)
②成员变量(包括普通成员变量和基类)
类的method有
im_class(指向类)
im_func(指向某个函数,这个函数实际上定义在了内存某个地方,不会和全局的冲突)
im_self(对类的method来说始终为None)
实例的method有
im_class(指向类)
im_func(指向某个函数,这个函数实际上定义在了内存某个地方,和类的方法的im_func一致)
im_self(指向实例本身)
事实上一个类实例化会将成员都指向类的成员,当类成员变动时,实例成员也会变动,但实例成员变化后,实例成员和类成员无关,以下为更新类的方法(类的__slots____metaclass__如果发生了变化,无法正确更新,待解决

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)

###全局变量的更新
普通的全局变量直接重新赋值就好
更改实例的则需要单独设定

update_instance_class = [(old_class, new_class), ]

def update_instance_class_x(x):
    # 在这里自定义实例迁移方法
    pass

def after_reload():
    # 定义迁移后回调
    pass

# 上面应该都是迁移者定义的内容
# 下面为热更新处理逻辑

def get_reload_modules():
    # 待填充获取所有需要reload的模块的方法 返回形式为[(old_module, new_module), ]
    pass

def update_instance(old_class, new_class):
    for x in gc.get_referrers(old_class):
        if isinstance(x, old_class):
            update_class_x(x)
for old_module, new_module in get_reload_modules():
    update_module(old_module, new_module)
for old_class, new_class in update_instance_class:
    update_instance(old_class, new_class)
after_reload()

###模块的更新

def update_module(old_module, new_module):
    old_module_dict = old_module.__dict__
    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)

热更新的流程

比较old和new的代码,记录更新的模块
1.重载需要更新的模块
2.查找需要更新的模块的引用(即这个依赖这个模块的模块,例如b.c依赖b)
3.重载依赖更新模块的模块
4.更新全局变量
##现在的问题
如果在init里面增加了属性定义,对于旧对象来说init是不会再次执行的,因此没有机会创建属性。
获取所有需要reload的模块的方法
类的__slots____metaclass__如果发生了变化,无法正确更新,待解决
函数替换如果前后一个一个无装饰器则不能替换,需要绕过python源码限制
inspect.getmro() 获取到的是tuple是一个类的继承顺序

发表评论

电子邮件地址不会被公开。 必填项已用*标注