Python:使用__import__时执行相对导入?

fdm*_*ion 7 python import

以下是此测试中的文件:

main.py
app/
 |- __init__.py
 |- master.py
 |- plugin/
 |-  |- __init__.py
 |-  |- p1.py
 |-  |_ p2.py
Run Code Online (Sandbox Code Playgroud)

我们的想法是拥有一个支持插件的应用程序.新的.py或.pyc文件可以放入与我的API相关的插件中.

master.py在应用程序级别有一个文件,其中包含任何和所有插件可能需要访问的全局变量和函数,以及应用程序本身.出于此测试的目的,"app"由app/__ init__.py中的测试函数组成.在实践中,应用程序可能会被移动到单独的代码文件,但之后我只是import master在该代码文件中使用以引入引用master.

这是文件内容:

main.py:

import app

app.test()
app.test2()
Run Code Online (Sandbox Code Playgroud)

应用程序/ __ init__.py:

import sys, os

from plugin import p1

def test():
        print "__init__ in app is executing test"
        p1.test()

def test2():
        print "__init__ in app is executing test2"
        scriptDir = os.path.join ( os.path.dirname(os.path.abspath(__file__)), "plugin" )
        print "The scriptdir is %s" % scriptDir
        sys.path.insert(0,scriptDir)
        m = __import__("p2", globals(), locals(), [], -1)
        m.test()
Run Code Online (Sandbox Code Playgroud)

应用程序/ master.py:

myVar = 0
Run Code Online (Sandbox Code Playgroud)

应用/插件/ __ init__.py:

<empty file>
Run Code Online (Sandbox Code Playgroud)

应用/插件/ p1.py:

from .. import master

def test():
    print "test in p1 is running"
    print "from p1: myVar = %d" % master.myVar
Run Code Online (Sandbox Code Playgroud)

应用/插件/ p2.py:

from .. import master

def test():
    master.myVar = 2
    print "test in p2 is running"
    print "from p2, myVar: %d" % master.myVar
Run Code Online (Sandbox Code Playgroud)

由于我明确导入p1模块,一切都按预期工作.但是,当我__import__用来导入p2时,我收到以下错误:

__init__ in app is executing test
test in p1 is running
from p1: myVar = 0
__init__ in app is executing test2
The scriptdir is ....../python/test1/app/plugin
Traceback (most recent call last):
  File "main.py", line 4, in <module>
    app.test2()
  File "....../python/test1/app/__init__.py", line 17, in test2
    m = __import__("p2", globals(), locals(), [], -1)
  File "....../python/test1/app/plugin/p2.py", line 1, in <module>
    from .. import master
ValueError: Attempted relative import in non-package
Run Code Online (Sandbox Code Playgroud)

执行继续执行test()函数并且错误输出正确,因为test2()尝试执行其__import__语句,而p2又尝试执行相对导入(当通过import语句显式导入p1时,它起作用,召回)

很明显,使用__import__不同于使用import语句的东西.Python文档声明使用import只是在__import__内部转换为语句,但必须有更多的进展,而不是满足于眼睛.

由于应用程序是基于插件的,因此在主应用程序中编写显式导入语句当然不可行.在内部使用导入本身

我在这里错过了什么?在手动导入模块时,如何让Python按预期运行__import__?似乎我可能没有完全理解相对导入的想法,或者我只是缺少关于导入发生位置的东西(即在函数内部而不是在代码文件的根部)

编辑:我发现以下可能,但不成功的解决方案:

m = __import__("p2",globals(),locals(),"plugin")
Run Code Online (Sandbox Code Playgroud)

(返回与上面相同的确切错误)

m = __import__("plugin",fromlist="p2")
Run Code Online (Sandbox Code Playgroud)

(返回对app.plugin的引用,而不是app.plugin.p2)

m = __import__("plugin.p2",globals(),locals())
Run Code Online (Sandbox Code Playgroud)

(返回对app.plugin的引用,而不是app.plugin.p2)

import importlib
m = importlib.import_module("plugin.p2")
Run Code Online (Sandbox Code Playgroud)

(返回:)

Traceback (most recent call last):
  File "main.py", line 4, in <module>
    app.test2()
  File "....../python/test1/app/__init__.py", line 20, in test2
    m = importlib.import_module("plugin.p2")
  File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/importlib/__init__.py", line 37, in import_module
    __import__(name)
ImportError: No module named plugin.p2
Run Code Online (Sandbox Code Playgroud)

Rem*_*ing 5

我遇到了类似的问题。
__import__如果所有父__init__.py文件都为空,则仅导入子模块。您应该改用 importlib

import importlib

p2 = importlib.import_module('plugin.p2')
Run Code Online (Sandbox Code Playgroud)


fdm*_*ion 1

我从未找到解决方案,所以我最终决定重组该程序。

我所做的是将主应用程序设置为一个类。然后,我也把每个插件改成了一个类。然后,当我使用import加载插件时,我还会实例化每个具有预定义名称的插件内的类,并传入对主应用程序类的引用。

这意味着每个类都可以通过使用引用直接读取和操作宿主类中的变量。它是完全灵活的,因为主机类导出的任何内容都可以被所有插件访问。

事实证明,这更有效,并且不依赖于相对路径和任何其他东西。这也意味着一个 Python 解释器理论上可以同时运行主机应用程序的多个实例(例如在不同的线程上),并且插件仍将引用正确的主机实例。

这基本上是我所做的:

主要.py:

import os, os.path, sys

class MyApp:

    _plugins = []

    def __init__(self):
        self.myVar = 0

    def loadPlugins(self):
        scriptDir = os.path.join ( os.path.dirname(os.path.abspath(__file__)), "plugin" )   
        sys.path.insert(0,scriptDir)
        for plug in os.listdir(scriptDir):
            if (plug[-3:].lower() == ".py"):
                m = __import__(os.path.basename(plug)[:-3])
                self._plugins.append(m.Plugin(self))

    def runTests(self):
        for p in self._plugins:
            p.test()

if (__name__ == "__main__"):
    app = MyApp()
    app.loadPlugins()
    app.runTests()
Run Code Online (Sandbox Code Playgroud)

插件/p1.py:

class Plugin:

    def __init__(self, host):
        self.host = host

    def test(self):
        print "from p1: myVar = %d" % self.host.myVar
Run Code Online (Sandbox Code Playgroud)

插件/p2.py:

class Plugin:

    def __init__(self, host):
        self.host = host

    def test(self):
        print "from p2: variable set"
        self.host.myVar = 1
        print "from p2: myVar = %d" % self.host.myVar
Run Code Online (Sandbox Code Playgroud)

有一些改进的空间,例如,验证每个导入的 .py 文件以查看它是否实际上是一个插件等等。但这按预期工作。