如何动态导入模块并将其名称作为字符串?

发布于 2024-07-08 10:39:48 字数 1330 浏览 8 评论 0原文

我正在编写一个将命令作为参数的 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)

该函数导入模块name,可能使用给定的globalslocals来确定如何在包上下文中解释该名称。 fromlist 给出了应从 name 给出的模块导入的对象或子模块的名称。

来源:https://docs.python.org/3/library/ function.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()

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

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


另请参阅: 如何在给定完整路径的情况下动态导入模块?

I'm writing a Python application that takes a command as an argument, for example:

$ python myapp.py command1

I want the application to be extensible, that is, to be able to add new modules that implement new commands without having to change the main application source. The tree looks something like:

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

So I want the application to find the available command modules at runtime and execute the appropriate one.

Python defines an __import__() function, which takes a string for a module name:

__import__(name, globals=None, locals=None, fromlist=(), level=0)

The function imports the module name, potentially using the given globals and locals to determine how to interpret the name in a package context. The fromlist gives the names of objects or submodules that should be imported from the module given by name.

Source: https://docs.python.org/3/library/functions.html#__import__

So currently I have something like:

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

command_module.run()

This works just fine, I'm just wondering if there is possibly a more idiomatic way to accomplish what we are doing with this code.

Note that I specifically don't want to get in to using eggs or extension points. This is not an open-source project and I don't expect there to be "plugins". The point is to simplify the main application code and remove the need to modify it each time a new command module is added.


See also: How can I import a module dynamically given the full path?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

墨落成白 2024-07-15 10:39:48

对于早于 2.7/3.1 的 Python,这几乎就是您的做法。

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

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

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

直接从 深入了解 Python

With Python older than 2.7/3.1, that's pretty much how you do it.

For newer versions, see importlib.import_module for Python 2 and Python 3.

Or using __import__ you can import a list of modules by doing this:

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

Ripped straight from Dive Into Python.

剩一世无双 2024-07-15 10:39:48

对于 Python 2.7 和 3.1 及更高版本,推荐方法是使用 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')

The recommended way for Python 2.7 and 3.1 and later is to use importlib module:

importlib.import_module(name, package=None)

Import a module. The name argument specifies what module to import in absolute or relative terms (e.g. either pkg.mod or ..mod). If the name is specified in relative terms, then the package argument must be set to the name of the package which is to act as the anchor for resolving the package name (e.g. import_module('..mod', 'pkg.subpkg') will import pkg.mod).

e.g.

my_module = importlib.import_module('os.path')
一身软味 2024-07-15 10:39:48

注意:自 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

Note: imp is deprecated since Python 3.4 in favor of importlib

As mentioned the imp module provides you loading functions:

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

I've used these before to perform something similar.

In my case I defined a specific class with defined methods that were required.
Once I loaded the module I would check if the class was in the module, and then create an instance of that class, something like this:

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
债姬 2024-07-15 10:39:48

使用 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)

# Verify contents of the module:
print(dir(module))

从这里开始,module< /code> 将是代表 pluginX 模块的模块对象(与通过执行 import pluginX 分配给 pluginX 的内容相同)。 因此,要调用 pluginX 中定义的 hello 函数(不带参数),请使用 module.hello()

要获得 from 模块“导入”功能的效果,请将其存储在已加载模块的内存缓存中,然后执行相应的 from 导入

sys.modules[module_name] = module

from pluginX import hello
hello()

要导入包,调用 import_module 就足够了。 假设当前工作目录下有一个包文件夹pluginX; 然后就做

import importlib

pkg = importlib.import_module('pluginX')

# check if it's all there..
print(dir(pkg))

Using importlib

Importing a source file

Here is a slightly adapted example from the documentation:

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)

# Verify contents of the module:
print(dir(module))

From here, module will be a module object representing the pluginX module (the same thing that would be assigned to pluginX by doing import pluginX). Thus, to call e.g. a hello function (with no parameters) defined in pluginX, use module.hello().

To get the effect "importing" functionality from the module instead, store it in the in-memory cache of loaded modules, and then do the corresponding from import:

sys.modules[module_name] = module

from pluginX import hello
hello()

Importing a package

To import a package instead, calling import_module is sufficient. Suppose there is a package folder pluginX in the current working directory; then just do

import importlib

pkg = importlib.import_module('pluginX')

# check if it's all there..
print(dir(pkg))
岁月打碎记忆 2024-07-15 10:39:48

使用 imp 模块,或更直接的 __import__() 函数。

Use the imp module, or the more direct __import__() function.

浅忆流年 2024-07-15 10:39:48

如果你想在本地使用它:

>>> 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()

If you want it in your locals:

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

same would work with globals()

落花浅忆 2024-07-15 10:39:48

您可以使用 exec

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

You can use exec:

exec("import myapp.commands.%s" % command)
被你宠の有点坏 2024-07-15 10:39:48

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

Similar as @monkut 's solution but reusable and error tolerant described here 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
七色彩虹 2024-07-15 10:39:48

下面的内容对我有用:

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

The below piece worked for me:

>>>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();

if you want to import in shell-script:

python -c '<above entire code in one line>'
苏璃陌 2024-07-15 10:39:48

以下内容对我有用:

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

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

The following worked for me:

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]())

It loads modules from the folder 'modus'. The modules have a single class with the same name as the module name. E.g. the file modus/modu1.py contains:

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

The result is a list of dynamically loaded classes "adapters".

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文