Як імпортувати модуль з назвою його як рядок?


543

Я пишу програму Python, яка в якості аргументу приймає, наприклад:

$ python myapp.py command1

Я хочу, щоб програма була розширюваною, тобто мати можливість додавати нові модулі, які реалізують нові команди, не змінюючи основне джерело програми. Дерево виглядає приблизно так:

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

Тому я хочу, щоб програма знаходила наявні командні модулі під час виконання та виконувала відповідний.

Python визначає функцію __import__ , яка бере рядок для імені модуля:

__import __ (ім'я, глобалі = Ні, місцеві жителі = Немає, зі списку = (), рівень = 0)

Функція імпортує ім'я модуля, потенційно використовуючи задані глобали та локалі, щоб визначити, як інтерпретувати ім'я в контексті пакета. Список списку дає назви об'єктів або підмодулів, які слід імпортувати з модуля, заданого іменем.

Джерело: 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()

Це працює чудово, мені просто цікаво, чи існує ідіоматичніший спосіб досягти того, що ми робимо з цим кодом.

Зауважте, що я спеціально не хочу вступати у використання яєць або пунктів розширення. Це не проект з відкритим кодом, і я не очікую, що там будуть "плагіни". Суть у тому, щоб спростити основний код програми та усунути необхідність його модифікувати кожного разу, коли додається новий командний модуль.


Що робить listlist = ["myapp.commands"]?
Пітер Мюллер

@ PieterMüller: в пітона оболонки типу це: dir(__import__). У списку списку має бути список імен, що емулюються "від імпорту імені ...".
mawimawi


Станом на 2019 рік слід шукати importlib: stackoverflow.com/a/54956419/687896
Brandt

Не використовуйте __import__ див. Python Doc використовуйте importlib.import_module
fcm

Відповіді:


321

З Python старше 2,7 / 3,1, це майже все, що ви робите.

Більш новіші версії див. importlib.import_moduleДля Python 2 та Python 3 .

Ви можете використовувати, execякщо хочете.

Або за допомогою __import__можна імпортувати список модулів, виконавши це:

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

Зірвав прямо з занурення в пітон .


7
що є різницею для виконання?
користувач1767754

1
Як ви могли використовувати __init__цей модуль?
Доріан Доре

7
Однією з проблем цього рішення для ОП є те, що захоплення винятків для одного або двох модулів "невідомих команд" змушує цілий командний додаток вийти з ладу за одним винятком. Особисто я б хотів за циклами над кожним імпортом, окремо загорнутим у спробу: mods = __ import __ () \ nexcept ImportError як помилка: звіт (помилка), щоб інші команди могли продовжувати працювати, поки погані будуть виправлені.
DevPlayer

7
Ще одна проблема цього рішення, як зазначає Денис Маліновський нижче, полягає в тому, що самі документи-пітони рекомендують не використовувати __import__. 2.7 документа: "Оскільки ця функція призначена для використання інтерпретатором Python, а не для загального використання, краще використовувати importlib.import_module () ..." При використанні python3, impмодуль вирішує цю проблему, як згадує monkut нижче.
LiavK

2
@JohnWu навіщо тобі, foreachколи маєш for element inі map()хоч :)
cowbert

300

Рекомендованим способом для Python 2.7 та 3.1 та пізніших версій є використання importlibмодуля:

importlib.import_module (ім'я, пакет = Немає)

Імпорт модуля. Аргумент імені вказує, який модуль імпортувати в абсолютних або відносних показниках (наприклад, pkg.mod або ..mod). Якщо ім'я вказано у відносних термінах, тоді аргумент пакета повинен бути встановлений на ім'я пакету, який повинен виступати якорем для вирішення імені пакета (наприклад, 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 *?
Нам Г ВУ

5
Я отримую відповідь тут stackoverflow.com/a/44492879/248616 тобто. викликglobals().update(my_module.__dict)
Нам Г ВУ

2
@NamGVU, це небезпечний метод, оскільки він забруднює ваші глобальні та може замінити будь-які ідентифікатори з однаковими іменами. Посилання, яке ви опублікували, має кращу, вдосконалену версію цього коду.
Денис Малиновський

132

Примітка: імп застаріло з Python 3.4 на користь importlib

Як згадувалося, модуль імпульсів надає функції завантаження:

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? Чому б не перевірити спочатку скомпільовану версію, а потім вихідну версію. Очікування класу зменшує можливість використання у вашому випадку.
стамат

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
Голова вгору: це рішення працює; однак модуль імпульсів буде застарілим на користь імпорту-ліб, див. сторінку імпульсу: "" "Застаріло з версії 3.4: Пакет імп очікує на припинення на користь імпорту." ""
Рікардо


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)

Таким чином ви можете отримати доступ до членів (наприклад, до функції " hello") зі свого модуля pluginX.py- у цьому фрагменті, що викликається module- під його простором імен; Наприклад, module.hello().

Якщо ви хочете імпортувати учасників (наприклад, " hello"), ви можете включити module/ pluginXв список пам'яті модулів:

sys.modules[module_name] = module

from pluginX import hello
hello()

Імпортуйте пакет

Імпорт пакету ( наприклад , 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після цього
Даніель Браун

@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()функції явно попереджає , що «зміст цього словника не повинно бути змінено».
мартіно

9

Ви можете використовувати exec:

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

Як я можу отримати ручку до модуля через exec, щоб я міг викликати (у цьому прикладі) метод .run ()?
Каміль Кісієль

5
Потім ви можете зробити getattr (myapp.commands, команда) для доступу до модуля.
Грег Х'югілл

1
... або додайте as command_moduleдо кінця імпортної заяви і потім зробітьcommand_module.run()
oxfn

У деяких версіях Python 3+ exec був перетворений у функцію з додатковою перевагою, що результуюча посилання, створена у джерелі, зберігається у аргументі exec () localals (). Для подальшої ізоляції посилання exec'd від локального блоку коду місцевих жителів ви можете надати свій власний дикт, як порожній та посилатися на посилання за допомогою цього диктату, або передати цей дікт іншим функціям exec (джерело, 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-script:

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
Будь ласка, не ховайте відповідей, не вказуючи причин у коментарях. Це нікому не допомагає.
Ребс

Я хотів би знати, чому це погані речі.
DevPlayer

Так само, як і я, оскільки я новачок у Python і хочу знати, чому це погано.
Космос

5
Це погано не тільки тому, що це поганий пітон, але також це загалом поганий код. Що таке fl? Визначення циклу for є надмірно складним. Шлях кодується важко (і, наприклад, не має значення). Він використовує відсторонений python ( __import__), для чого все це fl[i]? Це в основному нечитабельно і є надмірно складним для чогось, що не все так важко - дивіться відповідь, що голосує вгорі, її однокласником.
Філ
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.