2013-06-27 7 views
7

Вот файлы в этом тесте:Python: выполнять относительный импорт при использовании __import__?

main.py 
app/ 
|- __init__.py 
|- master.py 
|- plugin/ 
|- |- __init__.py 
|- |- p1.py 
|- |_ p2.py 

Идея заключается в том, чтобы иметь плагин с поддержкой приложений. Новые .py или .pyc-файлы можно отбросить в плагины, которые соответствуют моему API.

У меня есть файл master.py на уровне приложения, который содержит глобальные переменные и функции, к которым может иметь доступ любой плагин, а также само приложение. Для целей этого теста «приложение» состоит из тестовой функции в app/__ init__.py. На практике приложение, вероятно, будет перемещено в отдельный файл (ы) кода, но тогда я бы просто использовал import master в этом файле кода, чтобы привести ссылку на master.

Вот содержимое файла:

main.py:

import app 

app.test() 
app.test2() 

приложение/__ 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() 

приложение/master.py:

myVar = 0 

app/plugin/__ init__.py:

<empty file> 

приложение/плагин/p1.py:

from .. import master 

def test(): 
    print "test in p1 is running" 
    print "from p1: myVar = %d" % master.myVar 

приложение/плагин/p2.py:

from .. import master 

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

Поскольку я явно импортировать 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 

выполнения продолжается весь путь через функцию тест() и ошибку прямо в test2() пытается выполнить свою __import__ заявления, которое в своей очереди, p2 пытается сделать относительный импорт (который делает работы, когда p1 импортируются явно через оператор импорта, напомнит)

Очевидно, что использование __import__ это делать что-то другое, чем при использовании import заявления. В документах Python указано, что использование импорта просто переводит на заявление __import__ внутри, но должно быть больше, чем кажется на первый взгляд.

Поскольку приложение основано на плагинах, кодирование явных операторов импорта в главном приложении, конечно, нецелесообразно. Использование самого импорта в пределах

Что мне здесь не хватает? Как я могу заставить Python вести себя так, как ожидалось, при ручном импорте модулей с использованием __import__? Кажется, возможно, я не совсем понимаю идею относительного импорта или что у меня просто отсутствует что-то относительно того, где происходит импорт (т.внутри функции, а не в корне файла кода)

EDIT: Я нашел следующие возможные, но неудачные решения:

m = __import__("p2",globals(),locals(),"plugin") 

(возвращает ту же самую точную ошибку, как указано выше)

m = __import__("plugin",fromlist="p2") 

(возвращает ссылку на app.plugin, чтобы не app.plugin.p2)

m = __import__("plugin.p2",globals(),locals()) 

(возвращает повторно Ференц к app.plugin, не app.plugin.p2)

import importlib 
m = importlib.import_module("plugin.p2") 

(возвращается :)

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 
+0

Возможный дубликат http://stackoverflow.com/questions/3439082/referring -to-class-names-through-strings – blazetopher

+0

В качестве альтернативы вы можете сделать что-то вроде того, что я предложил в своем ответе на этот вопрос ... по существу получить список файлов в приложении/master/plugin и загрузить их динамически: http://stackoverflow.com/questions/17251008/python-call-a-constructor-whose-name-is-stored-in-a-variable/ – blazetopher

+0

@blazetopher Вы говорите об устранении master.py и просто используете глобальное пространство имен? Как-то это в лучшем случае кажется немного опасным - возможные столкновения имен и т. Д.? – fdmillion

ответ

1

Я никогда не найти решение, поэтому я в конечном итоге решение о реструктуризации программы.

Что я сделал, было настроено основное приложение как класс. Затем я также изменил каждый плагин на класс. Затем, когда я загружаю плагины с использованием импорта, я также создаю экземпляр класса внутри каждого плагина с предопределенным именем и передаю ссылку на основной класс приложения.

Это означает, что каждый класс может непосредственно читать и манипулировать переменными в классе хоста, просто используя ссылку. Это полностью гибко, потому что ничего, что экспорт хост-класса доступен все плагины.

Это оказывается более эффективным и не зависит от относительных путей и любого из этого материала. Это также означает, что один интерпретатор Python может теоретически запускать несколько экземпляров узла одновременно (например, для разных потоков), и плагины будут ссылаться на правильный экземпляр хоста.

Вот в основном то, что я сделал:

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

плагин/p1.py:

class Plugin: 

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

    def test(self): 
     print "from p1: myVar = %d" % self.host.myVar 

плагин/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 

Существует некоторая возможность улучшить это, например, проверяя каждый импортированный файл .py, чтобы увидеть, действительно ли это плагин и так далее. Но это работает так, как ожидалось.

3

У меня была аналогичная проблема.
__import__ импортирует только подмодули, если все родительские __init__.py файлы пустые. Вы должны использовать importlib вместо

import importlib 

p2 = importlib.import_module('plugin.p2') 
+0

Извините, не работал ... см. Выше. Я не дал понять, но я использую Python 2, поэтому, возможно, поэтому ... Я думаю, importlib - это в основном Python3? – fdmillion

+1

Вы можете использовать его в версии 2.7 – RickyA