如何导入给定名称的模块为字符串?


543

我正在编写一个以命令作为参数的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提供应从名称给定的模块中导入的对象或子模块的名称。

来源: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üller2012年

@PieterMüller:在python shell中键入: dir(__import__)。fromlist应该是模仿“ from name import ...”的名称列表。
mawimawi'8


截至2019年,您应该寻找importlibstackoverflow.com/a/54956419/687896
Brandt

不要使用__import__,请参阅Python Doc 使用importlib.import_module
fcm

Answers:


321

使用2.7 / 3.1之前的Python,这就是您要做的事情。

对于较新的版本,看到importlib.import_modulePython的2和和Python 3中

您也可以使用exec

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

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

直接从Dive Into Python翻录。


7
与执行人员有何不同?
user1767754

1
您如何使用__init__该模块?
Dorian Dore 2015年

7
这种针对OP的解决方案的问题是,捕获一个或两个错误的“命令”模块的异常会使他/她的整个命令应用程序因一个异常而失败。就个人而言,我希望遍历每个单独的导入循环:mods = __ import __()\ n除了ImportError作为错误:report(error)允许其他命令继续运行,而不良命令得以修复。
DevPlayer 2015年

7
正如Denis Malinovsky在下面指出的那样,此解决方案的另一个问题是python文档本身建议不要使用__import__。2.7 docs:“由于此函数供Python解释器使用,而不是一般用途,因此最好使用importlib.import_module()...”使用python3时,该imp模块解决了此问题,如下monkut所述。
LiavK

2
@JohnWu为什么你需要foreach时,你有for element inmap()虽然:)
cowbert

300

对于Python 2.7和3.1及更高版本,推荐的方法是使用importlib模块:

importlib.import_module(名称,包=无)

导入模块。name参数以绝对或相对方式指定要导入的模块(例如pkg.mod或..mod)。如果使用相对术语指定名称,则必须将package参数设置为充当解析包名称的定位符的包的名称(例如import_module('.. mod','pkg.subpkg')将导入pkg.mod)。

例如

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

2
由哪个来源或权威推荐?
michuelnik

66
文档建议不要使用__import__功能来支持上述模块。
丹尼斯·马利诺夫斯基

8
这对于导入效果很好os.path; 怎么样from os.path import *
Nam G VU

5
我在这里得到答案stackoverflow.com/a/44492879/248616即。致电globals().update(my_module.__dict)
Nam G VU

2
@NamGVU,这是一种危险的方法,因为它污染了您的全局变量,并且可以覆盖具有相同名称的所有标识符。您发布的链接对此代码进行了更好的改进。
Denis Malinovsky

132

注意:自Python 3.4以来不推荐使用imp,而建议使用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

2
好的简单解决方案。我写了类似的文章:stamat.wordpress.com/dynamic-module-import-in-python但您有一些缺陷:异常情况如何?IOError和ImportError?为什么不先检查编译版本,然后再检查源版本。期望一个类会降低您案例的可重用性。
stamat 2013年

3
在目标模块中构造MyClass的行中,向类名添加了冗余引用。它已经存储在其中,expected_class因此您可以class_inst = getattr(py_mod,expected_name)()代替。
安德鲁

1
请注意,如果存在正确匹配的字节编译文件(后缀为.pyc或.pyo),则将使用该文件代替解析给定的源文件https://docs.python.org/2/library/imp.html#imp.load_source
cdosborn

1
注意:此解决方案有效;但是,将不建议使用imp模块,而建议使用import lib,请参见imp的页面:“”“从3.4版起不推荐使用:imp软件包正待弃用,而建议使用importlib。”“”
Ricardo


15

现在,您应该使用importlib

导入源文件

文档实际上提供了一个配方,它像这样:

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/__init__.py)非常简单:

import importlib

pkg = importlib.import_module('pluginX')

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

3
sys.modules[module_name] = module如果您希望import module_name以后可以添加到代码的末尾
Daniel Braun

@DanielBraun这样做会使我得到错误> ModuleNotFoundError
Nam G VU

11

如果您想在本地人中使用它:

>>> 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()明确警告“此字典的内容不应修改”。
martineau

9

您可以使用exec

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

如何通过exec获取模块的句柄,以便可以调用(在此示例中).run()方法?
卡米尔·基西尔

5
然后,您可以执行getattr(myapp.commands,command)来访问该模块。
格雷格(Greg Hewgill)

1
...或添加as command_module到import语句的末尾,然后执行command_module.run()
oxfn

在某些版本的Python 3+中,exec被转换为函数,其附加好处是,在源代码中创建的结果引用被存储到exec()的locals()参数中。为了进一步将exec's引用与本地代码块locals隔离开来,您可以提供自己的dict,例如一个空的dict,并使用该dict引用引用,或者将该dict传递给其他函数exec(source,gobals(),command1_dict) .... print(command1_dict ['somevarible'])
DevPlayer

3

与@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

1

以下内容对我有用:

>>>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>'

-2

例如,我的模块名称类似于jan_module/ feb_module/ mar_module

month = 'feb'
exec 'from %s_module import *'%(month)

-7

以下为我工作:

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”加载模块。模块具有与模块名称相同名称的单个类。例如,文件modus / modu1.py包含:

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

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


13
请不要在未提供原因的情况下掩盖答案。它没有帮助任何人。
Rebs

我想知道为什么这是坏东西。
DevPlayer

与我相同,因为我刚接触Python并想知道,为什么这样做不好。
Kosmos

5
这是不好的,不仅因为它是不好的python,而且还是一般来说是不好的代码。什么fl啊 for循环的定义过于复杂。路径是硬编码的(例如,不相关)。它使用的是不鼓励使用的python(__import__),这些fl[i]东西都有什么用?这基本上是不可读的,并且对于并非那么困难的事情来说,它不必要地复杂-请参阅带有其一线的最高投票答案。
菲尔(Phil)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.