• python 动态导入模块,实现模块热更新


    最近有个部署需求,需要读取py文件格式的配置项,我的实现思路是把配置文件解析到内存中。主要使用两种方法:

    1. importlib.import_module
    2. types.ModuleType

    方法1、使用 import_module 动态导包

            先来看看 import module 使用方法。

    •  主要有两个参数:
      • package:包名
      • name:模块名
    • 返回 module 对象

    现在开始实现动态导包,成功读取到配置项。

    1. import importlib
    2. settings = importlib.import_module("remote_settings")

    这样子就能初步实现动态倒入了,但是我有个需求,就是我的系统好些个模块,用FOR循环导包,然后处理业务。然后问题来了,对同一个“包”导入多次,python并不会重新导入,而是返回内存缓存中该模块的地址。

    下面验证一下,第一次写入a = 123,第二次写入a = "hello"。

     输出结果,两次都是打印旧版本的变量,可见对同一个模块进行多次import_module,并不能实现热更新

    必须要reload,模块才会更新。

     输出结果如下,动态reload后,成功获得新版本a的值。 

    到此基本实现初步热更新需求了,但是还有个问题:

            问题一:重新加载的模块不删除旧版本在符号表中的登记项,比如旧版本中存在变量a,新版本中删除了该变量,但是重载不会更新该变化。

    1. def load_module(module_name):
    2. module = importlib.import_module(module_name)
    3. return importlib.reload(module)
    4. def rewrite_file(file_name, content):
    5. with open(file_name, "w+") as f:
    6. f.write(content)
    7. def main():
    8. rewrite_file(file_name, "a=123\nb=456")
    9. c1 = load_module(module_name)
    10. print(hasattr(c1, "a"))
    11. rewrite_file(file_name, "c=100\nd=200")
    12. c1 = load_module(module_name)
    13. print(hasattr(c1, "a"))

              我们期望输出 True、False,但是两次都是输出True,也就是说重新加载的模块不会删除最初旧版本模块在符号表中的登记项。

    方法2、使用 types.ModuleType 创建模块对象

            手动创建module对象,而不是使用内存中的module对象。这种方法不需要判断是否需要重载,而且是真正的更新,会删除旧版本模块的登记项。

    1. import types
    2. def import_from_pyfile(filename):
    3. d = types.ModuleType("config") # 创建一个模块对象
    4. d.__file__ = filename
    5. try:
    6. with open(filename, "r") as config_file:
    7. exec(compile(config_file.read(), filename, "exec"), d.__dict__)
    8. except ImportError as e:
    9. print("failt to read config file: {}".format(filename))
    10. raise e
    11. return d

            下面验证一下

            我们期望的输出依次是True、False,符合需求

            因此,这种方法能让我们的模块实现真正的重载。 

     

    一些注意事项

            无论是方法1还是方法2,都是返回一个module对象,module对象存在一些共性问题。

             问题一:重新加载类不影响类的任何已存实例,已存实例将继续使用原来的定义,只有重新加载后创建的新实例使用新定义。

    1. # 原先的 Dog 定义
    2. # class Dog():
    3. # def __init__(self):
    4. # self.name = None
    5. c1 = load_module(module_name)
    6. old_dog = c1.Dog()
    7. # 中间去修改了 Dog 定义
    8. # class Dog():
    9. # def __init__(self):
    10. # self.name = "旺财"
    11. c1 = load_module(module_name)
    12. new_dog = c1.Dog()
    13. print(old_dog.name, new_dog.name)
    14. >>> ouput:
    15. None 旺财

         

            问题二:模块内的引用,不会被reload。比如模块configA中引用了其他模块(configB),当configB发生变化,重新加载configA,并不会对configB进行重载。

            

            预期应该依次输出 configB version1、configB version2,但是输出了两次configB version1,这说明了模块内的引用,不会被reload,需要手动更新它。

            我这实现了一个递归更新方法,不仅对当前模块热更新,还更新里面所有的引用。

    1. def load_module(module):
    2. if isinstance(module, str): # 首次import
    3. module = importlib.import_module(module)
    4. return importlib.reload(module)
    5. def reload_module(module):
    6. load_module(module)
    7. for key, child_module in vars(module).items():
    8. if isinstance(child_module, types.ModuleType):
    9. reload_module(child_module)

            效果如下:

    1. def test_reload_module():
    2. configA = "config"
    3. configB = "./configB.py"
    4. configC = "./configC.py"
    5. rewrite_file(configB, "import configC\nname ='configB version1'")
    6. rewrite_file(configC, "name ='configC version1'")
    7. confA = load_module(configA)
    8. print("原始configB.name:", confA.configB.name)
    9. print("原始configC.name:", confA.configB.configC.name)
    10. a = 123
    11. rewrite_file(configB, "import configC\nname ='configB version2'")
    12. rewrite_file(configC, "name ='configC version2'")
    13. confA = load_module(configA)
    14. print("非递归重载configA, configB.name:", confA.configB.name)
    15. print("非递归重载configA, configC.name:", confA.configB.configC.name)
    16. reload_module(confA)
    17. print("递归重载configA, configB.name:", confA.configB.name)
    18. print("递归重载configA, configC.name:", confA.configB.configC.name)

            日志如下:

     

  • 相关阅读:
    web api 在线更新设备代码
    Box Muller 法生成正态分布随机数
    手机备忘录怎么扫描图片上的文字
    百年难遇,四款简约大气的神仙软件,每一款都能惊艳到你
    Vue计算属性 computed
    【面试经典150 | 数组】轮转数组
    从JDK8到JDK18,Java垃圾回收的详细解答
    自制OS 5-1==用C语言不用C库写内核。一个独立内核OS的制作
    校园篮球网页作业成品 运动系列NBA篮球主题 学校篮球网页制作模板 学生简单体育运动网站设计成品
    批量执行redis命令总结
  • 原文地址:https://blog.csdn.net/weixin_40647516/article/details/126537108