python 动态导入模块实现模块热更新的方法

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

  • importlib.import_module
  • types.ModuleType

方法1、使用 import_module 动态导包

先来看看import module使用方法。

  • 主要有两个参数:

    • package:包名
    • name:模块名
  • 返回 module 对象

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

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

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

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

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

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

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

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

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

def load_module(module_name):
    module = importlib.import_module(module_name)
    return importlib.reload(module)

def rewrite_file(file_name, content):
    with open(file_name, "w+") as f:
        f.write(content)

def main():

    rewrite_file(file_name, "a=123\nb=456")
    c1 = load_module(module_name)
    print(hasattr(c1, "a"))

    rewrite_file(file_name, "c=100\nd=200")
    c1 = load_module(module_name)
    print(hasattr(c1, "a"))

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

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

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

import types

def import_from_pyfile(filename):
    d = types.ModuleType("config")  # 创建一个模块对象
    d.__file__ = filename

    try:
        with open(filename, "r") as  config_file:
            exec(compile(config_file.read(), filename, "exec"), d.__dict__)
    except ImportError as e:
        print("failt to read config file: {}".format(filename))
        raise e

    return d

下面验证一下

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

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

一些注意事项

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

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

# 原先的 Dog 定义
# class Dog():
#     def __init__(self):
#         self.name = None
c1 = load_module(module_name)
old_dog = c1.Dog()

# 中间去修改了 Dog 定义
# class Dog():
#     def __init__(self):
#         self.name = "旺财"
c1 = load_module(module_name)
new_dog = c1.Dog()

print(old_dog.name, new_dog.name)

>>> ouput:
None 旺财

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

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

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

def load_module(module):
    if isinstance(module, str):  # 首次import
        module = importlib.import_module(module)
    return importlib.reload(module)

def reload_module(module):
    load_module(module)

    for key, child_module in vars(module).items():
        if isinstance(child_module, types.ModuleType):
            reload_module(child_module)

效果如下:

def test_reload_module():
    configA = "config"
    configB = "./configB.py"
    configC = "./configC.py"
    rewrite_file(configB, "import configC\nname ='configB version1'")
    rewrite_file(configC, "name ='configC version1'")

    confA = load_module(configA)
    print("原始configB.name:", confA.configB.name)
    print("原始configC.name:", confA.configB.configC.name)

    a = 123
    rewrite_file(configB, "import configC\nname ='configB version2'")
    rewrite_file(configC, "name ='configC version2'")

    confA = load_module(configA)
    print("非递归重载configA, configB.name:", confA.configB.name)
    print("非递归重载configA, configC.name:", confA.configB.configC.name)

    reload_module(confA)
    print("递归重载configA, configB.name:", confA.configB.name)
    print("递归重载configA, configC.name:", confA.configB.configC.name)

日志如下:

到此这篇关于python动态导入模块,实现模块热更新的文章就介绍到这了,更多相关python模块热更新内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Python动态导入模块:__import__、importlib、动态导入的使用场景实例分析

    本文实例讲述了Python动态导入模块:__import__.importlib.动态导入的使用场景.分享给大家供大家参考,具体如下: 相关内容: __import__ importlib 动态导入的使用场景 首发时间:2018-02-23 16:06 __import__: 功能: 是一个函数,可以在需要的时候动态导入模块 使用: __import__(模块名) 但对于多级目录,只会导入第一级 目录结构: mo1=__import__("des") mo2=__import__(&q

  • Python 动态导入对象,importlib.import_module()的使用方法

    背景 一个函数运行需要根据不同项目的配置,动态导入对应的配置文件运行. 解决 文件结构 a #文件夹 │a.py │__init__.py b #文件夹 │b.py │__init__.py ├─c#文件夹 │c.py │__init__.py # c.py 中内容 args = {'a':1} class C: def c(self): pass 目的 向a模块中导入c.py 中的对象 解决方案 a.py import importlib params = importlib.import_m

  • Python动态导入模块的方法实例分析

    本文实例讲述了Python动态导入模块的方法.分享给大家供大家参考,具体如下: 一.正常导入模块 正常模块导入方式: import module(模块路径) 同时导入多个模块: import os,sys,socket 二.动态导入模块 动态导入模块允许我们通过字符串形式来导入模块 2.1 __import__函数,接受一个字符串参数 import os, sys my_sys = __import__('sys') my_os = __import__('os') print(sys.vers

  • Python importlib动态导入模块实现代码

    阅读目录 一般而言,当我们需要某些功能的模块时(无论是内置模块或自定义功能的模块),可以通过import module 或者 from * import module的方式导入,这属于静态导入,很容易理解. 而如果当我们需要在程序的运行过程时才能决定导入某个文件中的模块时,并且这些文件提供了同样的接口名字,上面说的方式就不适用了,这时候需要使用python 的动态导入. importlib使用 如在scripts目录中保存着一些功能模块,向外提供类似的接口poc()和脚本描述信息descript

  • Python动态导入模块和反射机制详解

    一.前言 何谓动态导入模块,就是说模块的导入可以根据我们的需求动态的去导入,不是像一般的在代码文件开头固定的导入所需的模块. 何谓反射机制,利用字符串的形式在模块或对象中操作(查找/获取/删除/添加)成员. 下面进入具体实例介绍环节.先创建一个示例文件example.py,简单写入几个加减乘除函数,如下,方便下文讲解使用. flag = 1 # 此变量在介绍反射机制时会用到 def my_sum(a, b): return a + b def my_sub(a, b): return a - b

  • Python在游戏中的热更新实现

    目录 介绍: 原理: 1.标准import 2.reload函数 实现: 总结: 介绍: 热更新,就是在服务器不重启的的情况下,对游戏增加新的功能或者修复出现bug 的代码.游戏更新迭代速度快,催生了热更技术的需求,在我经历过的游戏项目中,无论是服务端还是客户端,版本的更新都是围绕着热更新,特别是现在游戏动辄几个G,每次让玩家下载完整的包不现实,随意游戏必须要支持热更.下面来谈一下客户端Python热更新的处理. 原理: 1.标准import 都知道Python提供了import可以导入一个标准

  • python 动态导入模块实现模块热更新的方法

    最近有个部署需求,需要读取py文件格式的配置项,我的实现思路是把配置文件解析到内存中.主要使用两种方法: importlib.import_module types.ModuleType 方法1.使用 import_module 动态导包 先来看看import module使用方法. 主要有两个参数: package:包名 name:模块名 返回 module 对象 现在开始实现动态导包,成功读取到配置项. import importlib settings = importlib.import

  • SpringBoot EasyPoi动态导入导出的两种方式实现方法详解

    目录 前言 一.基于@Excel的 isColumnHidden 属性 1.1 实现原理 1.2 实现步骤 1.3 实现效果 二. 基于List< ExcelExportEntity > 的导出 实现效果 总结 前言 一开始为了图方便,使用的是土方法,即创建多个不同的实体类,每个实体类对应不同的列.这样虽说能实现,但实在不想多复制实体类,把代码堆的和shi山一样.于是查看官方文档,里面确实提供了更加优雅的实现方式.废话不多说,开整. 一.基于@Excel的 isColumnHidden 属性

  • python如何导入自己的模块

    目录 如何导入自己的模块 1.自定义模块与所需要调用自定义模块的文件在同一文件夹下 2.自定义模块与所需要调用自定义模块的文件不在同一文件夹下 3.找到本地库的路径 4.手动给python添加搜索路径 导入本地模块报错的问题 以下图为例说明 如何导入自己的模块 在实际的编程生活当中,我们除了会去import已经存在的包外,当然还会偶尔自定义一些模块,然后来导入,其实一般而言,自定义的模块也就是一系列的脚本,具体的,自己写好了脚本,然后导入,导入的情况分为以下情况: 1.自定义模块与所需要调用自定

  • webpack热模块替换(HMR)/热更新的方法

    这是一篇关于webpack热模块替换的最简单的配置(不需要react),也称作热更新. 模块热替换(HMR)的作用是,在应用运行时,无需刷新页面,便能替换.增加.删除必要的模块. HMR 对于那些由单一状态树构成的应用非常有用.因为这些应用的组件是 "dumb" (相对于 "smart") 的,所以在组件的代码更改后,组件的状态依然能够正确反映应用的最新状态. webpack-dev-server内置"live reload",会自动刷新页面.

  • websocket4.0+typescript 实现热更新的方法

    最近搞了一个webpack4+typescript的开发环境,折腾了很久现在记录一下.... 本身环境比较好搞,但是热更新是个麻烦事儿 本环境是基于webpack-dev-server搭建的 output: { publicPath: '/dist', path: path.resolve(__dirname, 'dist'), filename: 'ljax.bundle.js', hotUpdateChunkFilename: 'hot/hot-update.js', hotUpdateMa

  • Node.js巧妙实现Web应用代码热更新

    背景 相信使用 Node.js 开发过 Web 应用的同学一定苦恼过新修改的代码必须要重启 Node.js 进程后才能更新的问题.习惯使用 PHP 开发的同学更会非常的不适用,大呼果然还是我大PHP才是世界上最好的编程语言.手动重启进程不仅仅是非常恼人的重复劳动,当应用规模稍大以后,启动时间也逐渐开始不容忽视. 当然作为程序猿,无论使用哪种语言,都不会让这样的事情折磨自己.解决这类问题最直接和普适的手段就是监听文件修改并重启进程.这个方法也已经有很多成熟的解决方案提供了,比如已经被弃坑的 nod

随机推荐