本文的目的是介绍Python动态导入-如何从变量中的模块名称中导入*?的详细情况,特别关注python动态导入模块的相关信息。我们将通过专业的研究、有关数据的分析等多种方式,为您呈现一个全面的了解Py
本文的目的是介绍Python动态导入-如何从变量中的模块名称中导入*?的详细情况,特别关注python 动态导入模块的相关信息。我们将通过专业的研究、有关数据的分析等多种方式,为您呈现一个全面的了解Python动态导入-如何从变量中的模块名称中导入*?的机会,同时也不会遗漏关于Python 3.5+:如何在给定完整文件路径的情况下动态导入模块(存在隐式同级导入)?、python importlib动态导入模块、python 动态导入包、python 动态导入模块实现模块热更新的方法的知识。
本文目录一览:- Python动态导入-如何从变量中的模块名称中导入*?(python 动态导入模块)
- Python 3.5+:如何在给定完整文件路径的情况下动态导入模块(存在隐式同级导入)?
- python importlib动态导入模块
- python 动态导入包
- python 动态导入模块实现模块热更新的方法
Python动态导入-如何从变量中的模块名称中导入*?(python 动态导入模块)
作为讨论在这里,我们可以使用字符串变量动态导入模块。
import importlib
importlib.import_module('os.path')
我的问题是如何import *
从字符串变量?
像这样的东西暂时不起作用
importlib.import_module('os.path.*')
Python 3.5+:如何在给定完整文件路径的情况下动态导入模块(存在隐式同级导入)?
题
标准库清楚地说明了如何直接导入源文件(给定源文件的绝对文件路径),但是如果该源文件使用隐式同级导入(如以下示例中所述),则此方法不起作用。
在隐式同级导入的情况下,该示例如何适应工作?
设置/示例
这是一个说明性的例子
目录结构:
root/ - directory/ - app.py - folder/ - implicit_sibling_import.py - lib.py
app.py
:
import osimport importlib.util# construct absolute pathsroot = os.path.abspath(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))isi_path = os.path.join(root, ''folder'', ''implicit_sibling_import.py'')def path_import(absolute_path): ''''''implementation taken from https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly'''''' spec = importlib.util.spec_from_file_location(absolute_path, absolute_path) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return moduleisi = path_import(isi_path)print(isi.hello_wrapper())
lib.py
:
def hello(): return ''world''
implicit_sibling_import.py
:
import lib # this is the implicit sibling import. grabs root/folder/lib.pydef hello_wrapper(): return "ISI says: " + lib.hello()#if __name__ == ''__main__'':# print(hello_wrapper())
python folder/implicit_sibling_import.py
使用该if __name__ ==''__main__'':
块运行注释掉了ISI says: world
Python 3.6中的收益。
但是运行python directory/app.py
收益:
Traceback (most recent call last): File "directory/app.py", line 10, in <module> spec.loader.exec_module(module) File "<frozen importlib._bootstrap_external>", line 678, in exec_module File "<frozen importlib._bootstrap>", line 205, in _call_with_frames_removed File "/Users/pedro/test/folder/implicit_sibling_import.py", line 1, in <module> import libModuleNotFoundError: No module named ''lib''
解决方法
如果我想补充import sys; sys.path.insert(0,os.path.dirname(isi_path))
到app.py
,pythonapp.py
产量world
如预期,但我想避免改写(munging)的sys.path
,如果可能的。
回答要求
我想python app.py
打印,ISI says: world
并希望通过修改path_import
功能来完成此操作。
我不确定搞砸的含义sys.path
。例如。如果有directory/requests.py
并且我将路径添加directory
到了sys.path
,我不想importrequests
开始导入directory/requests.py
而不是导入我安装的请求库pip install requests
。
解决方案 必须
实现为python函数,该函数接受指向所需模块的绝对文件路径并返回模块对象。
理想情况下,该解决方案不应引入副作用(例如,如果确实进行了修改sys.path
,则应返回sys.path
其原始状态)。如果解决方案确实带来了副作用,则应说明为什么不引入副作用就无法实现解决方案。
PYTHONPATH
如果我有多个项目正在执行此操作,则无需记住PYTHONPATH
每次在它们之间切换时都要进行设置。用户应该只能够pipinstall
运行我的项目,而无需任何其他设置即可运行它。
-m
该-m
标志是推荐的/
pythonic方法,但是标准库也清楚地说明了如何直接导入源文件。我想知道如何适应这种方法来处理隐式相对导入。显然,Python的内部结构必须执行此操作,因此内部结构与“直接导入源文件”文档有何不同?
答案1
小编典典我能想到的最简单的解决方案是sys.path
在执行导入的函数中进行临时修改:
from contextlib import contextmanager@contextmanagerdef add_to_path(p): import sys old_path = sys.path sys.path = sys.path[:] sys.path.insert(0, p) try: yield finally: sys.path = old_pathdef path_import(absolute_path): ''''''implementation taken from https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly'''''' with add_to_path(os.path.dirname(absolute_path)): spec = importlib.util.spec_from_file_location(absolute_path, absolute_path) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module
除非您同时在另一个线程中进行导入,否则这不会引起任何问题。否则,由于sys.path
已还原到其先前的状态,因此不应有有害的副作用。
编辑:
我意识到我的回答有些不尽人意,但是,深入研究代码后发现,该行spec.loader.exec_module(module)
基本上导致exec(spec.loader.get_code(module.__name__),module.__dict__)
被调用。这spec.loader.get_code(module.__name__)
只是lib.py中包含的代码。
因此,对该问题的更好答案将必须找到一种方法import
,只需通过exec语句的第二个参数简单地注入一个或多个全局变量,即可使该语句具有不同的行为。但是,“
@所做的任何使导入机制在该文件的文件夹中显示的操作,都必须持续超出初始导入的持续时间,因为调用该文件时该文件中的函数可能会执行进一步的导入”,如@问题评论中的user2357112。
不幸的是,更改import
语句行为的唯一方法似乎是更改sys.path
或打包__path__
。module.__dict__
已经包含__path__
让似乎并没有工作,这叶子sys.path
(或者试图找出为什么执行不会代码当作一个包,即使它有__path__
和__package__
…
-但我不知道从哪里开始-也许它有与没有__init__.py
文件有关)。
此外,这个问题似乎并不特定于Sibling package imports,importlib
而是一个普遍的问题。
Edit2:
如果您不希望该模块最终出现在sys.modules
下面,则应该可以正常工作(请注意,sys.modules
导入期间添加的所有模块都将被 删除
):
from contextlib import contextmanager@contextmanagerdef add_to_path(p): import sys old_path = sys.path old_modules = sys.modules sys.modules = old_modules.copy() sys.path = sys.path[:] sys.path.insert(0, p) try: yield finally: sys.path = old_path sys.modules = old_modules
python importlib动态导入模块
一般而言,当我们需要某些功能的模块时(无论是内置模块或自定义功能的模块),可以通过import module 或者 from * import module的方式导入,这属于静态导入,很容易理解。
而如果当我们需要在程序的运行过程时才能决定导入某个文件中的模块时,并且这些文件提供了同样的接口名字,上面说的方式就不适用了,这时候需要使用python 的动态导入。
importlib使用
如在scripts目录中保存着一些功能模块,向外提供类似的接口poc()和脚本描述信息description,需要传入一个参数target,当然脚本执行的功能是不一样的,以下只是举例:
starnight:EXP-M starnight$ ls scripts/
__init__.py __pycache__ test1.py test2.py test3.py
starnight:EXP-M starnight$ cat scripts/test1.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
description = ''it is a test1''
def poc(target):
print(''it is a test1'')
return True
而我们需要动态传入脚本名,来选用此时要执行的功能:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import importlib
script_name = input(''please input script_name : '') # 手动输入脚本名
module = importlib.import_module(''scripts.{}''.format(script_name)) # 动态导入相应模块
func = module.poc('''') # 执行脚本功能
print(module.description) # 获取脚本描述信息
please input script_name : test1
it is a test1
it is a test1
...
please input script_name : test3
it is a test3
it is a test3
当我们动态给定脚本名字时,就会动态的导入该模块,执行相应的功能。
importlib其他介绍
python doc: importlib
importlib中的几个函数:__import__、import_module、find_loader、invalidate_caches、reload
"Note Programmatic importing of modules should use import_module() instead of this function."
当进行编程时,使用import_module,如上使用该模块。
find_loader用来查找模块,reload重新载入模块,invalidate_caches不多介绍了。
python 动态导入包
在廖雪峰老师的 python 教程实战中的编写 web 框架一节中,见(https://www.liaoxuefeng.com/wiki/1016959663602400/1018490695712544),其中有一个函数 add_routes,代码如下:
def add_routes(app, module_name):
n = module_name.rfind(''.'')
if n == (-1):
mod = __import__(module_name, globals(), locals())
else:
name = module_name[n+1:]
mod = getattr(__import__(module_name[:n], globals(), locals(), [name]), name)
for attr in dir(mod):
if attr.startswith(''_''):
continue
fn = getattr(mod, attr)
if callable(fn):
method = getattr(fn, ''__method__'', None)
path = getattr(fn, ''__route__'', None)
if method and path:
add_route(app, fn)
在廖雪峰老师代码的基础上,我作了一些修改。
使用相对简单的 import_module 方法来动态导入视图函数(控制器)的包,而后使用 walk_packages 方法来递归动态导入上述包中的所有模块。
import importlib
import pkgutil
base = importlib.import_module(''app.controller'')
for loader, module_name, is_pkg in pkgutil.walk_packages(base.__path__, f''{base.__name__}.''):
try:
sub_module = __import__(module_name, fromlist=[''get_submodule''])
except ImportError as e:
raise e
# 后面代码略
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模块热更新内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!
- python中模块导入模式详解
- python如何导入自己的模块
- python导入同级模块的实现
- python基础之模块的导入
- 简单谈谈Python中的模块导入
- 在python中实现导入一个需要传参的模块
- 浅谈Python模块导入规范
- python中添加模块导入路径的方法
- python模块导入方式浅析步骤
今天关于Python动态导入-如何从变量中的模块名称中导入*?和python 动态导入模块的讲解已经结束,谢谢您的阅读,如果想了解更多关于Python 3.5+:如何在给定完整文件路径的情况下动态导入模块(存在隐式同级导入)?、python importlib动态导入模块、python 动态导入包、python 动态导入模块实现模块热更新的方法的相关知识,请在本站搜索。
本文标签: