ChatGPT解决这个技术问题 Extra ChatGPT

如何以字符串形式导入模块?

我正在编写一个将命令作为参数的 Python 应用程序,例如:

$ python myapp.py command1

我希望应用程序是可扩展的,也就是说,能够添加实现新命令的新模块,而无需更改主应用程序源。这棵树看起来像:

myapp/
    __init__.py
    commands/
        __init__.py
        command1.py
        command2.py
    foo.py
    bar.py

所以我希望应用程序在运行时找到可用的命令模块并执行适当的命令模块。

Python 定义了一个 __import__() 函数,该函数接受一个字符串作为模块名称:

__import__(name, globals=None, locals=None, fromlist=(), level=0) 该函数导入模块名称,可能使用给定的全局变量和局部变量来确定如何在包上下文中解释名称。 fromlist 给出了应该从 name 给定的模块导入的对象或子模块的名称。来源:https://docs.python.org/3/library/functions.html#__import__

所以目前我有类似的东西:

command = sys.argv[1]
try:
    command_module = __import__("myapp.commands.%s" % command, fromlist=["myapp.commands"])
except ImportError:
    # Display error message

command_module.run()

这工作得很好,我只是想知道是否可能有一种更惯用的方式来完成我们用这段代码所做的事情。

请注意,我特别不想使用鸡蛋或扩展点。这不是一个开源项目,我不希望有“插件”。重点是简化主应用程序代码,并且无需在每次添加新命令模块时对其进行修改。

fromlist=["myapp.commands"] 有什么作用?
@PieterMüller:在 python shell 中输入:dir(__import__)。 fromlist 应该是模拟“从名称导入...”的名称列表。
截至 2019 年,您应该寻找 importlibstackoverflow.com/a/54956419/687896
不要使用 __import__ 请参阅 Python Doc 使用 importlib.import_module

J
JacKeown

对于早于 2.7/3.1 的 Python,这几乎就是您的操作方式。

对于较新的版本,请参阅 Python 2Python 3importlib.import_module

如果您愿意,也可以使用 exec

或者使用 __import__,您可以通过执行以下操作导入模块列表:

>>> moduleNames = ['sys', 'os', 're', 'unittest'] 
>>> moduleNames
['sys', 'os', 're', 'unittest']
>>> modules = map(__import__, moduleNames)

直接从 Dive Into Python 撕下。


exec有什么区别?
OP 的这种解决方案的一个问题是,为一个或两个错误的“命令”模块捕获异常会使他/她的整个命令应用程序因一个异常而失败。就个人而言,我会循环遍历每个导入单独包装在 try: mods=__import__()\nexcept ImportError as error: report(error) 以允许其他命令继续工作,而坏的命令得到修复。
正如 Denis Malinovsky 在下面指出的那样,此解决方案的另一个问题是 python 文档本身不建议使用 __import__。 2.7 文档:“因为这个函数是供 Python 解释器使用的,而不是一般用途,所以最好使用 importlib.import_module()...”当使用 python3 时,imp 模块解决了这个问题,如蒙库特下面提到。
@JohnWu 为什么在拥有 for element inmap() 时还需要 foreach :)
请注意,地图在 python 3 中不起作用,因为地图现在使用惰性求值。
m
martineau

Python 2.7 和 3.1 及更高版本的 recommended 方式是使用 importlib 模块:

importlib.import_module(name, package=None) 导入一个模块。 name 参数以绝对或相对术语(例如 pkg.mod 或 ..mod)指定要导入的模块。如果名称以相对术语指定,则 package 参数必须设置为作为解析包名称的锚点的包名称(例如 import_module('..mod', 'pkg.subpkg')将导入 pkg.mod)。

例如

my_module = importlib.import_module('os.path')

由哪个来源或权威机构推荐?
文档建议 against 使用 __import__ 函数来支持上述模块。
这适用于 import os.pathfrom os.path import * 怎么样?
我在这里得到答案stackoverflow.com/a/44492879/248616,即。呼叫 globals().update(my_module.__dict)
@NamGVU,这是一种危险的方法,因为它会污染您的全局变量并且可以覆盖任何具有相同名称的标识符。您发布的链接具有此代码的更好、改进的版本。
B
Brian Burns

注意:imp 自 Python 3.4 起已弃用,取而代之的是 importlib

如前所述,imp 模块为您提供加载功能:

imp.load_source(name, path)
imp.load_compiled(name, path)

我以前用过这些来执行类似的操作。

就我而言,我定义了一个特定的类,其中包含所需的已定义方法。一旦我加载了模块,我会检查该类是否在模块中,然后创建该类的一个实例,如下所示:

import imp
import os

def load_from_file(filepath):
    class_inst = None
    expected_class = 'MyClass'

    mod_name,file_ext = os.path.splitext(os.path.split(filepath)[-1])

    if file_ext.lower() == '.py':
        py_mod = imp.load_source(mod_name, filepath)

    elif file_ext.lower() == '.pyc':
        py_mod = imp.load_compiled(mod_name, filepath)

    if hasattr(py_mod, expected_class):
        class_inst = getattr(py_mod, expected_class)()

    return class_inst

好的和简单的解决方案。我写了一个类似的:stamat.wordpress.com/dynamic-module-import-in-python 但是你的有一些缺陷:异常呢? IOError 和 ImportError?为什么不先检查编译版本,然后检查源版本。期望一个类会降低您的案例的可重用性。
在您在目标模块中构造 MyClass 的行中,您正在添加对类名的冗余引用。它已存储在 expected_class 中,因此您可以改为使用 class_inst = getattr(py_mod,expected_name)()
请注意,如果存在正确匹配的字节编译文件(后缀为 .pyc 或 .pyo),则将使用它而不是解析给定的源文件https://docs.python.org/2/library/imp.html#imp.load_source
注意:这个解决方案有效;然而,imp 模块将被弃用以支持 import lib,请参阅 imp 的页面:“”“自 3.4 版以来不推荐使用:imp 包正在等待弃用以支持 importlib。”“”
B
Brandt

现在您应该使用 importlib

导入源文件

docs 实际上为此提供了一个方法,它类似于:

import sys
import importlib.util

file_path = 'pluginX.py'
module_name = 'pluginX'

spec = importlib.util.spec_from_file_location(module_name, file_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)

# check if it's all there..
def bla(mod):
    print(dir(mod))
bla(module)

通过这种方式,您可以从模块 pluginX.py 访问成员(例如,函数“hello”)——在此代码段中称为 module——在其命名空间下;例如,module.hello()

如果要导入成员(例如,“hello”),您可以在内存中的模块列表中包含 module/pluginX

sys.modules[module_name] = module

from pluginX import hello
hello()

导入一个包

在当前目录下导入包(egpluginX/__init__.py)实际上很简单:

import importlib

pkg = importlib.import_module('pluginX')

# check if it's all there..
def bla(mod):
    print(dir(mod))
bla(pkg)

如果您希望之后能够 import module_name,请将 sys.modules[module_name] = module 添加到代码末尾
@DanielBraun 这样做让我出错 > ModuleNotFoundError
g
gimel

使用 imp module 或更直接的 __import__() 函数。


不要使用 __import__ 请参阅 Python Doc 使用 importlib.import_module
J
Jonathan Livni

如果你想在当地人那里得到它:

>>> mod = 'sys'
>>> locals()['my_module'] = __import__(mod)
>>> my_module.version
'2.6.6 (r266:84297, Aug 24 2010, 18:46:32) [MSC v.1500 32 bit (Intel)]'

同样适用于 globals()


内置 locals() 函数的 documentation 明确警告“不应修改此字典的内容”。
G
Georgy

您可以使用 exec

exec("import myapp.commands.%s" % command)

如何通过 exec 获取模块的句柄,以便可以调用(在本例中) .run() 方法?
然后,您可以执行 getattr(myapp.commands, command) 来访问该模块。
...或将 as command_module 添加到 import 语句的末尾,然后执行 command_module.run()
在 Python 3+ 的某些版本中,exec 被转换为一个函数,具有额外的好处,即在源代码中创建的结果引用被存储到 exec() 的 locals() 参数中。为了进一步将引用的 exec'd 与本地代码块 locals 隔离开,您可以提供自己的 dict,例如一个空的并使用该 dict 引用引用,或者将该 dict 传递给其他函数 exec(source, gobals(), command1_dict) .... print(command1_dict['somevarible'])
s
stamat

与@monkut 的解决方案类似,但此处描述的可重用和容错http://stamat.wordpress.com/dynamic-module-import-in-python/

import os
import imp

def importFromURI(uri, absl):
    mod = None
    if not absl:
        uri = os.path.normpath(os.path.join(os.path.dirname(__file__), uri))
    path, fname = os.path.split(uri)
    mname, ext = os.path.splitext(fname)

    if os.path.exists(os.path.join(path,mname)+'.pyc'):
        try:
            return imp.load_compiled(mname, uri)
        except:
            pass
    if os.path.exists(os.path.join(path,mname)+'.py'):
        try:
            return imp.load_source(mname, uri)
        except:
            pass

    return mod

P
PanwarS87

下面的作品对我有用:

>>>import imp; 
>>>fp, pathname, description = imp.find_module("/home/test_module"); 
>>>test_module = imp.load_module("test_module", fp, pathname, description);
>>>print test_module.print_hello();

如果要在 shell 脚本中导入:

python -c '<above entire code in one line>'

M
Marc de Lignie

以下对我有用:

import sys, glob
sys.path.append('/home/marc/python/importtest/modus')
fl = glob.glob('modus/*.py')
modulist = []
adapters=[]
for i in range(len(fl)):
    fl[i] = fl[i].split('/')[1]
    fl[i] = fl[i][0:(len(fl[i])-3)]
    modulist.append(getattr(__import__(fl[i]),fl[i]))
    adapters.append(modulist[i]())

它从文件夹“模式”加载模块。这些模块有一个与模块名称同名的类。例如文件 modus/modu1.py 包含:

class modu1():
    def __init__(self):
        self.x=1
        print self.x

结果是动态加载的类“适配器”列表。


请不要在没有在评论中提供理由的情况下隐藏答案。它对任何人都没有帮助。
我想知道为什么这是坏东西。
和我一样,因为我是 Python 新手,想知道为什么这很糟糕。
这很糟糕,不仅因为它是糟糕的 python,而且它通常只是糟糕的代码。 fl 是什么? for 循环定义过于复杂。路径是硬编码的(例如,不相关)。它正在使用不鼓励的 python (__import__),所有这些 fl[i] 的东西是干什么用的?这基本上是不可读的,并且对于不那么难的事情来说是不必要的复杂 - 请参阅其单线投票的最高投票答案。