# [Python] import不在package中的多個同名module ## 問題 有一次需要import 多個**同名**檔案,而這些檔案卻又不在package內,只是零散的module。 原先打算透過 `sys.path` 把module的目錄加進去直接import,做法如下: ```python= import sys sys.path.insert(0, './folder_01') import utils as utils_01 sys.path.insert(0, './folder_02') import utils as utils_02 print(utils_01) print(utils_02) ``` > <module 'utils' from './folder_01/utils.py'> > <module 'utils' from './folder_01/utils.py'> folder_01和folder_02底下都各有一個utils.py,輸出結果卻是相同的,也就是folder_02底下的utils.py沒有成功被import。 看起來是已經load過的module會放到`sys.modules`裡面,而已經存在的module就不會再次load,而是拿現有的來用。 所以如果只用`sys.path`看起來不能處理這個問題,而且如果重複交替使用這兩個module的時候,`sys.path`不做額外處理就會不斷被加長。 --- ## Delete imported module 刪除`sys.modules`內已經被load過的module。 ```python= import sys sys.path.insert(0, './folder_01') import utils as utils_01 print(utils_01) del sys.modules['utils'] # 加入這行 sys.path.insert(0, './folder_02') import utils as utils_02 print(utils_02) ``` > <module 'utils' from './folder_01/utils.py'> > <module 'utils' from './folder_02/utils.py'> 這樣的結果算是正確了,但每次使用都得delete module,並確保新的module目錄在`sys.path`中要優先被找到。 --- ## 使用importlib 在 https://stackoverflow.com/questions/6031584/importing-from-builtin-library-when-module-with-same-name-exists 這篇找到的做法: ```python= import importlib import sys def import_same_name(file_path, module_name): spec = importlib.util.spec_from_file_location(module_name, file_path) module = importlib.util.module_from_spec(spec) sys.modules[module_name] = module spec.loader.exec_module(module) return module module_01 = import_same_name('./folder_01/utils.py', 'utils_01') module_02 = import_same_name('./folder_02/utils.py', 'utils_02') print(module_01) print(module_02) ``` > <module 'utils' from './folder_01/utils.py'> > <module 'utils' from './folder_02/utils.py'> --- ## 避免重複import及自動改名的機制 延續上一個部分,在`import_same_name` function加入一些更便於使用的機制: * 先去`sys.modules`找是否有module的path與要加入的module path重複,若有則直接使用。 * module_name自動加入postfix編號,這樣一來使用`import_same_name`時就不用額外幫module取不同的名字。 ```python= import importlib import sys def search_module_by_path(file_path): ''' If module file path exists in sys.modules, return it. ''' for v in sys.modules: try: if file_path == v.__file__: return v except AttributeError: pass return None def import_same_name(file_path, module_name): module = search_module_by_path(file_path) if module is not None: # module has been imported. return module # 'utils' -> 'utils_2' if 'utils_0' and 'utils_1' exists. i = 0 module_name += '_{:d}' while module_name.format(i) in sys.modules: i += 1 module_name = module_name.format(i) spec = importlib.util.spec_from_file_location(module_name, file_path) module = importlib.util.module_from_spec(spec) sys.modules[module_name] = module spec.loader.exec_module(module) return module module_01 = import_same_name('./folder_01/utils.py', 'utils') module_02 = import_same_name('./folder_02/utils.py', 'utils') print(module_01) print(module_02) ``` > <module 'utils_0' from './folder_01/utils.py'> > <module 'utils_1' from './folder_02/utils.py'> --- ## 紀錄 * 在使用[Delete imported module](#Delete-imported-module)的方法時,如果先用`utils_01` create instance後,再照步驟import utils_02,先前的instance是可以正確運作的。 * 看起來最後的方法可能也可以透過以下方法完成,但很不方便就是了。 ```python= import sys import utils sys.modules['utils_01'] = sys.modules.pop('utils') import utils_01 ``` ###### tags: `python`