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.


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 .


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

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

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)

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


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)

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

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

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

-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.


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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.