Como importar um módulo que recebeu seu nome como string?

543

Estou escrevendo um aplicativo Python que usa como comando um argumento, por exemplo:

$ python myapp.py command1

Quero que o aplicativo seja extensível, ou seja, seja capaz de adicionar novos módulos que implementam novos comandos sem precisar alterar a fonte principal do aplicativo. A árvore se parece com:

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

Então, eu quero que o aplicativo encontre os módulos de comando disponíveis em tempo de execução e execute o apropriado.

Python define uma função __import__ , que recebe uma string para o nome de um módulo:

__import __ (nome, globais = Nenhum, locais = Nenhum, da lista = (), nível = 0)

A função importa o nome do módulo, potencialmente usando os globais e locais fornecidos para determinar como interpretar o nome em um contexto de pacote. A lista from fornece os nomes de objetos ou submódulos que devem ser importados do módulo fornecido pelo nome.

Fonte: https://docs.python.org/3/library/functions.html# import

Então, atualmente, tenho algo como:

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

command_module.run()

Isso funciona muito bem, só estou me perguntando se existe uma maneira mais idiomática de realizar o que estamos fazendo com esse código.

Note que eu especificamente não quero usar ovos ou pontos de extensão. Este não é um projeto de código aberto e não espero que haja "plugins". O objetivo é simplificar o código principal do aplicativo e remover a necessidade de modificá-lo toda vez que um novo módulo de comando é adicionado.

Kamil Kisiel
fonte
O que o fromlist = ["myapp.commands"] faz?
Pieter Müller
@ PieterMüller: em um shell python digite o seguinte: dir(__import__). A lista from from deve ser uma lista de nomes para emular "from name import ...".
mawimawi
A partir de 2019, você deve procurar importlib: stackoverflow.com/a/54956419/687896
Brandt
Não use __import__, consulte o Documento Python usar um importlib.import_module
fcm

Respostas:

321

Com o Python anterior a 2.7 / 3.1, é assim que você faz.

Para as versões mais recentes, ver importlib.import_modulepara Python 2 e e Python 3 .

Você pode usar execse quiser também.

Ou __import__você pode importar uma lista de módulos fazendo o seguinte:

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

Rasgado direto de Dive Into Python .

Harley Holcombe
fonte
7
qual é a diferença para exec?
user1767754
1
Como você pode usar __init__esse módulo?
Dorian Dore
7
Um problema com esta solução para o OP é que a captura de exceções para um ou dois módulos de "comando" incorretos faz com que todo o aplicativo de comando falhe em uma exceção. Pessoalmente, eu faria um loop sobre cada importação envolvida individualmente em uma tentativa: mods = __ import __ () \ n exceto ImportError como erro: report (error) para permitir que outros comandos continuem funcionando enquanto os maus são corrigidos.
DevPlayer
7
Outro problema com essa solução, como Denis Malinovsky aponta abaixo, é que os próprios documentos python recomendam não usar __import__. Os documentos 2.7: "Como essa função é para ser usada pelo interpretador Python e não para uso geral, é melhor usar importlib.import_module () ..." Ao usar o python3, o impmódulo resolve esse problema, como o monkut menciona abaixo.
LiavK
2
@JohnWu por que você precisa foreachquando você tem for element ine map()embora :)
cowbert
300

A maneira recomendada para o Python 2.7 e 3.1 e posterior é usar o importlibmódulo:

importlib.import_module (nome, pacote = Nenhum)

Importe um módulo. O argumento name especifica qual módulo importar em termos absolutos ou relativos (por exemplo, pkg.mod ou ..mod). Se o nome for especificado em termos relativos, o argumento do pacote deve ser definido como o nome do pacote que deve atuar como a âncora para resolver o nome do pacote (por exemplo, import_module ('.. mod', 'pkg.subpkg') importará o pkg.mod).

por exemplo

my_module = importlib.import_module('os.path')
Denis Malinovsky
fonte
2
Recomendado por qual fonte ou autoridade?
Michuelnik #
66
Documentação aconselha contra utilizando __import__função em favor do módulo acima mencionado.
Denis Malinovsky
8
Isso funciona bem para importação os.path; que tal from os.path import *?
Nam G VU
5
Eu recebo a resposta aqui stackoverflow.com/a/44492879/248616 ie. chamandoglobals().update(my_module.__dict)
Nam G VU
2
@NamGVU, este é um método perigoso, pois está poluindo seus globais e pode substituir qualquer identificador com o mesmo nome. O link que você postou tem uma versão melhor e melhorada deste código.
Denis Malinovsky
132

Nota: imp está obsoleto desde o Python 3.4 em favor do importlib

Como mencionado, o módulo imp fornece funções de carregamento:

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

Eu usei isso antes para executar algo semelhante.

No meu caso, defini uma classe específica com métodos definidos que eram necessários. Depois de carregar o módulo, verificaria se a classe estava no módulo e criaria uma instância dessa classe, algo como isto:

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
monkut
fonte
2
Solução boa e simples. Escrevi um semelhante: stamat.wordpress.com/dynamic-module-import-in-python Mas o seu tem algumas falhas: E as exceções? IOError e ImportError? Por que não verificar primeiro a versão compilada e depois a versão de origem. Esperar uma classe reduz a reutilização no seu caso.
Stamat
3
Na linha em que você constrói MyClass no módulo de destino, você está adicionando uma referência redundante ao nome da classe. Ele já está armazenado expected_classpara que você possa fazê-lo class_inst = getattr(py_mod,expected_name)().
Andrew
1
Observe que, se existir um arquivo compilado de bytes correspondente (com o sufixo .pyc ou .pyo), ele será usado em vez de analisar o arquivo de origem fornecido . https://docs.python.org/2/library/imp.html#imp.load_source
cdosborn
1
Atenção: esta solução funciona; no entanto, o módulo imp será descontinuado em favor da importação da lib, consulte a página do imp: "" "Descontinuado desde a versão 3.4: O pacote imp está pendente de descontinuação em favor do importlib." ""
Ricardo
15

Atualmente você deve usar importlib .

Importar um arquivo de origem

Os documentos realmente fornecem uma receita para isso, e é assim:

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)

Dessa forma, você pode acessar os membros (por exemplo, uma função " hello") do seu módulo pluginX.py- neste trecho chamado module- sob seu espaço de nome; Por exemplo module.hello(),.

Se você deseja importar os membros (por exemplo, " hello"), você pode incluir module/ pluginXna lista de módulos na memória:

sys.modules[module_name] = module

from pluginX import hello
hello()

Importar um pacote

A importação de um pacote ( por exemplo , pluginX/__init__.py) sob seu diretório atual é realmente simples:

import importlib

pkg = importlib.import_module('pluginX')

# check if it's all there..
def bla(mod):
    print(dir(mod))
bla(pkg)
Brandt
fonte
3
adicione sys.modules[module_name] = moduleno final do seu código se você quiser import module_namedepois #
Daniel Braun
@DanielBraun está me fazendo com erro> ModuleNotFoundError
Nam G VU
10

Se você quiser nos locais:

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

mesmo funcionaria com globals()

Jonathan
fonte
2
A documentação para a locals()função interna adverte explicitamente que "o conteúdo deste dicionário não deve ser modificado".
martineau 14/01
9

Você pode usar exec:

exec("import myapp.commands.%s" % command)
Greg Hewgill
fonte
Como obtenho um identificador para o módulo através do exec, para que eu possa chamar (neste exemplo) o método .run ()?
Kamil Kisiel
5
Você pode fazer o getattr (myapp.commands, command) para acessar o módulo.
Greg Hewgill 19/11/2008
1
... ou adicionar as command_moduleao final da instrução de importação e, em seguida, fazercommand_module.run()
oxfn
Em algumas versões do Python 3+, o exec foi convertido em uma função com o benefício adicional de que o resultante mencionado criado na fonte seja armazenado no argumento locals () de exec (). Para isolar ainda mais o exec referenciado dos locais do bloco de código local, você pode fornecer seu próprio dict, como um vazio, e referenciar as referências usando esse dict ou passá-lo para outras funções exec (source, gobals (), command1_dict) ... print (command1_dict ['somevarible']))
DevPlayer
2

Semelhante à solução da @monkut, mas reutilizável e tolerante a erros descrita aqui 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
stamat
fonte
0

A peça abaixo funcionou para mim:

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

se você deseja importar no shell-script:

python -c '<above entire code in one line>'
PanwarS87
fonte
-2

Por exemplo, os nomes dos meus módulos são como jan_module/ feb_module/ mar_module.

month = 'feb'
exec 'from %s_module import *'%(month)
chiru
fonte
-7

O seguinte funcionou para mim:

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

Carrega módulos da pasta 'modus'. Os módulos possuem uma única classe com o mesmo nome que o nome do módulo. Por exemplo, o arquivo modus / modu1.py contém:

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

O resultado é uma lista de classes "adaptadores" carregados dinamicamente.

Marc de Lignie
fonte
13
Por favor, não enterre respostas sem fornecer uma razão nos comentários. Não ajuda ninguém.
Rebs
Eu gostaria de saber por que isso é ruim.
DevPlayer
O mesmo que eu, desde que sou novo no Python e quero saber por que isso é ruim.
Kosmos
5
Isso é ruim não apenas porque é um python ruim, mas também é geralmente um código ruim. O que é fl? A definição do loop for é excessivamente complexa. O caminho é codificado permanentemente (e por exemplo, irrelevante). É usando python desanimado ( __import__), para que serve tudo isso fl[i]? Isso é basicamente ilegível e desnecessariamente complexo para algo que não é tão difícil - veja a resposta mais votada com uma única frase.
21817 Phil