Dynamic imports in python

How To Load / Import Python Package, Module Dynamically

Python provide built-in function __import__(name[, globals[, locals[, fromlist[, level]]]]) for us to load / import python module in python source code dynamically. The name parameter is the module name in string format. The fromlist parameter is a boolean value. When fromlist is True, it means the module name can contain package name ( for example lib.mod1), when fromlist is False, it means the module name should not contain the module package ( for example mod1 ).

The __import__() function returns the imported module object if import success. If the module does not exist, then it will throw ModuleNotFoundError error. This article will show you how to use the __import__() function with examples.

1. Load / Import Python Module Dynamically Example Steps.

python-dynamically-import-modules-example

  1. Create a PyDev project in eclipse ( How To Run Python In Eclipse With PyDev ).
  2. Create below package and modules, there are 3 modules mod1, mod2, mod3 in com.dev2qa.example.import.lib package. We will import the three modules dynamically in the TestImportFunction module.
  3. There are 2 methods in each lib module ( mod1, mod2, mod3 ).
  4. mod1.py
def method_1(): print("mod1.method_1()") def method_2(): print("mod1.method_2()")
def method_3(): print("mod1.method_3()") def method_4(): print("mod1.method_4()")
def method_5(): print("mod1.method_5()") def method_6(): print("mod1.method_6()")
# Import getmembers, isfunction function from inspect module. from inspect import getmembers, isfunction # This function will list all the functions and attributes of the module. def list_module_attributes(mod_name): # First import the module object by module name. imported_module = __import__(mod_name, globals=None, locals=None, fromlist=True) # The dir() function will return all attributes and functions of the module object. mod_attrs_str = dir(imported_module) print(mod_attrs_str) ''' The inspect.getmembers() function will get all members of the module object. The inspect.isfunction() function will check whether the module member is a function or not. Below code will return module functions only. ''' functions_list = [o for o in getmembers(imported_module) if isfunction(o[1])] # Each element in the functions_list is a tuple object. for func in functions_list: # The first element of the tuple is the function name. print(func[0]) # This function let you input multiple module name and import them one by one. def import_multiple_modules(): # Input module names separated by white space. inp = input("Input multiple module-name separated with white space: ").strip() # Split module names. args = inp.split(" ") # Loop in the module names list. for mod_name in args: try: ''' Import module by it's name, the fromlist = True make the module name can contain package name. The return value is the imported module object. ''' imported_module = __import__(mod_name, globals=None, locals=None, fromlist=True) # Print the imported moudle name. print(imported_module.__name__) # If the module do not exist, then throw ModuleNotFound error. except ModuleNotFoundError as e: print(e) # This function let you import one module and run the specified module function. def import_one_module(): # Let user input module name and function name. inp = input("Input module-name function-name separated with white space: ").strip() # Split the argument to get module-name and function-name. args = inp.split(" ") if(len(args) == 2): # Get module name and function name. mod_name = args[0] func_name = args[1] # Import the module by name, and return the module object. fromlist = True make the module name can contain package name. imported_module = __import__(mod_name, globals=None, locals=None, fromlist=True) #imported_module = __import__(mod_name, globals=None, locals=None, fromlist=False) # Print imported module name. print("Imported module name is ", imported_module.__name__) # If the module contain the input function. if(hasattr(imported_module, func_name)): # Get the function attribute. func = getattr(imported_module,func_name) # Run the function. func() else: print("Module ", mod_name, " do not has function ", func_name) else: print("Input arguments count error.") if __name__ == '__main__': import_one_module() #import_multiple_modules() #list_module_attributes('lib.mod1') #list_module_attributes('lib.mod2') #list_module_attributes('lib.mod3')
# When input correct module name and method name. >>>Input module-name function-name separated with white space: lib.mod1 method_1 Imported module name is lib.mod1 mod1.method_1() ------------------------------------------------------------------------ # When input correct module name and wrong method name >>>Input module-name function-name separated with white space: lib.mod1 method Imported module name is lib.mod1 Module lib.mod1 do not has function method ------------------------------------------------------------------------ # When input wrong module name. >>>Input module-name function-name separated with white space: lib.mod method_1 Traceback (most recent call last): File "/Users/songzhao/Documents/WorkSpace/dev2qa.com-example-code/PythonExampleProject/com/dev2qa/example/import/TestImportFuncton.py", line 67, in import_one_module() File "/Users/songzhao/Documents/WorkSpace/dev2qa.com-example-code/PythonExampleProject/com/dev2qa/example/import/TestImportFuncton.py", line 45, in import_one_module imported_module = __import__(mod_name, globals=None, locals=None, fromlist=True) ModuleNotFoundError: No module named 'lib.mod'
>>>Input multiple module-name separated with white space: lib.mod1 lib.mod lib.mod2 lib.mod1 No module named 'lib.mod' lib.mod2

2. Question & Answer.

2.1 How to fix ImportError: No module named mod when import python modules dynamically.

  1. There is a ‘modules’ directory in my python app, and there are two python modules module_1.py and module_2.py in the ‘modules’ directory. And my team member may add more python module files such as module_3.py, module_4.py …… in the modules directory, so I want to dynamically import all the python modules in the python app modules directory like below.
# First get all the module files name in the python app modules directory. all_module_files_array = [] all_module_files_array = os.listdir('modules') # Set the python modules directory to the system path variable. sys.path.append('/Users/jerry/test_python_app/modules') # Loop in the module files array. for mod in all_module_files_array: # Import the python module by file name. import mod

But when I run the above source code, it shows the ImportError: No module named mod error. How to fix this error?

# First import the python importlib module. import importlib # Get all the module files name in the python app modules folder. all_module_files_array = [] all_module_files_array = os.listdir('modules') . # Loop in the module files array. for mod in all_module_files_array: # Import the python mod with importlib.import_module() method. importlib.import_module(mod)

Источник

Читайте также:  Java получить размер файла

Динамический импорт модулей в Python

Давайте представим ситуацию, когда вам нужно установить на все виртуальные машины (агенты сервера сборки) определенный пакет Python. Но вы не можете изменить образ агента, а загрузка, к примеру из pypi.org или github.com непроверенных пакетов, ограничена. Как тут не вспомнить последние новости про вредоносные изменения в пакете nmp или более свежую информацию про PyPi.

Python использует подход под названием EAFP — Easier to ask for forgiveness, than permission (легче попросить прощения, чем разрешения). Это значит, что проще предположить, что что-то существует (к примеру, словарь в словаре, или в нашем случае модуль в системе) или получить ошибку в противном случае.

Этот подход, развитый в PEP-0302, позволяет делать хук импорта модулей, что в итоге приводит нас к возможности написания следующего кода:

Поскольку я часто сталкиваюсь с автоматизацией развертывания CI/CD с помощью Bamboo, то для меня естественным решением было разместить код выверенного модуля в артефактах JFrog и написать (согласно PEP-0302) классы finder и loader для загрузки и установки модулей. В моем случае код импорта модулей выглядит следующим образом:

Согласно PEP-0302 в протоколе участвуют два класса: finder и loader .

Продемонстрирую модуль amtImport.py, реализующий работу этих классов:

import sys import pip import os import requests import shutil class IntermediateModule: """ Module for paths like `artifactory_local.path` """ def __init__(self, fullname): self.__package__ = fullname self.__path__ = fullname.split('.') self.__name__ = fullname class ArtifactoryFinder: """ Handles `artifactory_local. ` modules """ def find_module(self, module_name, package_path): if module_name.startswith('artifactory_local'): return ArtifactoryLoader() class ArtifactoryLoader: """ Installs and imports modules from artifactory """ artifactory_modules: list = [] def _is_installed(self, fullname): try: self._import_module(fullname) ArtifactoryLoader.artifactory_modules.append(fullname) return True except ImportError: return False def _import_module(self, fullname): actual_name = '.'.join(fullname.split('.')[2:]) actual_name = actual_name.replace('_', '.').split('.')[0] return __import__(actual_name) def _install_module(self, fullname): if not self._is_installed(fullname): actual_name = '.'.join(fullname.split('.')[2:]) url = fullname.replace('artifactory_local', '').replace(actual_name, '').replace('_', '/') actual_name = actual_name.replace('_', '.') url = 'https://artifactory.app.local' + url.replace('.', '/') + actual_name auth = (os.getenv("bamboo_artifactory_user_name"), os.getenv("bamboo_artifactory_access_token_secret")) file_name = f"/" with requests.get(url=url, auth=auth, stream=True) as r: if r.status_code != 200: raise Exception(f"Status code : ") with open(file_name, 'wb') as f: shutil.copyfileobj(r.raw, f) pip.main(['install', file_name]) ArtifactoryLoader.artifactory_modules.append(fullname) def _is_repository_path(self, fullname): return fullname.count('.') == 2 def _is_intermediate_path(self, fullname): return fullname.count('.') < 2 def load_module(self, fullname): if self._is_repository_path(fullname): self._install_module(fullname) if self._is_intermediate_path(fullname): module = IntermediateModule(fullname) else: module = self._import_module(fullname) sys.modules[fullname] = module sys.meta_path.append(ArtifactoryFinder())

(Код тестировался для Python 3.6)

Итог: динамический импорт из артефактов можно лаконично оформить следующим кодом:

from amtImport import ArtifactoryLoader # pyright: reportMissingImports=false, reportUnusedImport=false from artifactory_local.artifactory_amt_Sealed import smbprotocol_1_9_0_tar_gz if "artifactory_local.artifactory_amt_Sealed.smbprotocol_1_9_0_tar_gz" in ArtifactoryLoader.artifactory_modules: print("Package successfully loaded")

Используя аналогичный подход, можно организовать импорт модулей из любого другого источника.

P.S. Основой для данной статьи послужил пакет import_from_github_com.

Источник

Оцените статью