首頁 > 軟體

python 動態匯入模組實現模組熱更新的方法

2022-08-30 14:02:30

最近有個部署需求,需要讀取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=123nb=456")
    c1 = load_module(module_name)
    print(hasattr(c1, "a"))
    
    rewrite_file(file_name, "c=100nd=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 configCnname ='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 configCnname ='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模組熱更新內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com