Python: как импортировать все методы и атрибуты из модуля динамически

Я хочу загрузить модуль динамически, учитывая его имя строки (из переменной среды). Я использую Python 2.7. Я знаю, что могу сделать что-то вроде:

import os, importlib
my_module = importlib.import_module(os.environ.get('SETTINGS_MODULE'))

Это примерно эквивалентно

import my_settings

(где SETTINGS_MODULE = 'my_settings'). Проблема в том, что мне нужно что-то эквивалентное

from my_settings import *

так как я хотел бы иметь доступ ко всем методам и переменным в модуле. Я пробовал

import os, importlib
my_module = importlib.import_module(os.environ.get('SETTINGS_MODULE'))
from my_module import *

но я получаю кучу ошибок. Есть ли способ импортировать все методы и атрибуты модуля динамически в Python 2.7?

Ответ 1

Если у вас есть объект модуля, вы можете имитировать использование логики import * следующим образом:

module_dict = my_module.__dict__
try:
    to_import = my_module.__all__
except AttributeError:
    to_import = [name for name in module_dict if not name.startswith('_')]
globals().update({name: module_dict[name] for name in to_import})

Однако это почти наверняка очень плохая идея. Вы будете бесцеремонно топтать любые существующие переменные с одинаковыми именами. Это плохо, если вы обычно выполняете from blah import *, но когда вы делаете это динамически, существует еще больше неопределенности в отношении того, какие имена могут столкнуться. Вам лучше просто импортировать my_module, а затем получить доступ к тому, что вам нужно от него, используя регулярный доступ к атрибутам (например, my_module.someAttr) или getattr, если вам нужно динамически обращаться к его атрибутам.

Ответ 2

Мой случай был немного другим - хотелось динамически импортировать имена констант .py в каждом модуле gameX.__init__.py (см. ниже), потому что статический импорт этих файлов оставит их в sys.modules навсегда (см. это выдержка из Beazley я выбрал из этот связанный вопрос).

Вот моя структура папок:

game/
 __init__.py
 game1/
   __init__.py
   constants.py
   ...
 game2/
   __init__.py
   constants.py
   ...

Каждый gameX.__init__.py экспортирует метод init(), поэтому изначально был from .constants import * во всех тех gameX.__init__.py, которые я пытался перемещать внутри метода init().

Моя первая попытка в строках:

@@ -275,2 +274,6 @@ def init():
     # called instead of 'reload'
+    yak = {}
+    yak.update(locals())
+    from .constants import * # fails here
+    yak = {x: y for x,y in locals() if x not in yak}
+    globals().update(yak)
     brec.ModReader.recHeader = RecordHeader

Ошибка с довольно загадочным:

SyntaxError: import * не разрешен в функции 'init', потому что он содержит вложенную функцию со свободными переменными

Я могу заверить вас, что в нем нет вложенных функций. Так или иначе, я взломал и разрезал и закончил:

def init():
    # ...
    from .. import dynamic_import_hack
    dynamic_import_hack(__name__)

Где в game.__init__.py:

def dynamic_import_hack(package_name):
    print __name__ # game.init
    print package_name # game.gameX.init
    import importlib
    constants = importlib.import_module('.constants', package=package_name)
    import sys
    for k in dir(constants):
        if k.startswith('_'): continue
        setattr(sys.modules[package_name], k, getattr(constants, k))

(для setattr см. Как добавить атрибуты модуля во время выполнения? while for getattr Как импортировать функция модуля python динамически? - Я предпочитаю использовать те, которые имеют прямой доступ к __dict__)

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

  • почему этот "SyntaxError: import *" не разрешен в функции "init", пока нет вложенных функций?
  • dir имеет много предупреждений в его документе - в частности, он пытается получить наиболее актуальную, а не полную информацию - это меня полностью беспокоит
  • Нет ли встроенного способа сделать import *? даже в python 3?

Ответ 3

Не отвечая точно на поставленный вопрос, но если вы хотите иметь файл в качестве прокси для динамического модуля, вы можете использовать возможность определения __getattr__ на уровне модуля.

import importlib
import os

module_name = os.environ.get('CONFIG_MODULE', 'configs.config_local')
mod = importlib.import_module(module_name)

def __getattr__(name):
    return getattr(mod, name)