Instanciação dinâmica do nome da string de uma classe no módulo importado dinamicamente?

179

Em python, eu tenho que instanciar certa classe, sabendo seu nome em uma string, mas essa classe 'vive' em um módulo importado dinamicamente. Um exemplo a seguir:

script da classe loader:

import sys
class loader:
  def __init__(self, module_name, class_name): # both args are strings
    try:
      __import__(module_name)
      modul = sys.modules[module_name]
      instance = modul.class_name() # obviously this doesn't works, here is my main problem!
    except ImportError:
       # manage import error

script de módulo carregado dinamicamente:

class myName:
  # etc...

Eu uso esse arranjo para fazer com que qualquer módulo carregado dinamicamente seja usado pela classe loader, seguindo certos comportamentos predefinidos nos módulos carregados por dyn ...

Javier Novoa C.
fonte

Respostas:

251

Você pode usar getattr

getattr(module, class_name)

para acessar a turma. Código mais completo:

module = __import__(module_name)
class_ = getattr(module, class_name)
instance = class_()

Conforme mencionado abaixo , podemos usar importlib

import importlib
module = importlib.import_module(module_name)
class_ = getattr(module, class_name)
instance = class_()
Sven Marnach
fonte
3
module = __import__(module, fromlist=[name])só funcionou para mim.
umpirsky
16
Se alguém está tendo problemas com o método de importação mencionado anteriormente, o código funcionou melhor usando o método a seguir importlib.import_module . Pode ser usado como: module = importlib.import_module (module_name)
jpennell
@jpennell você deve postar isso como uma resposta, muitas vezes é mais útil para ser capaz de usar diretamente a string retornado porobj.__module__
Anentropic
importlib.import_modulecarregará o arquivo .py em um pyc, se necessário, e manipulará a classe module.name.pathing.to.get.to.the a classe. __import__não fará nenhuma dessas coisas, em um ambiente de django (não testado fora disso) #
James James
122

tl; dr

Importe o módulo raiz importlib.import_modulee carregue a classe por seu nome usando a getattrfunção:

# Standard import
import importlib
# Load "module.submodule.MyClass"
MyClass = getattr(importlib.import_module("module.submodule"), "MyClass")
# Instantiate the class (pass arguments to the constructor, if needed)
instance = MyClass()

explicações

Você provavelmente não deseja usar __import__para importar dinamicamente um módulo por nome, pois ele não permite importar sub-módulos:

>>> mod = __import__("os.path")
>>> mod.join
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute 'join'

Aqui está o que o documento python diz sobre __import__:

Nota: Essa é uma função avançada que não é necessária na programação diária do Python, ao contrário de importlib.import_module ().

Em vez disso, use o importlibmódulo padrão para importar dinamicamente um módulo por nome. Com getattrvocê, você pode instanciar uma classe pelo nome:

import importlib
my_module = importlib.import_module("module.submodule")
MyClass = getattr(my_module, "MyClass")
instance = MyClass()

Você também pode escrever:

import importlib
module_name, class_name = "module.submodule.MyClass".rsplit(".", 1)
MyClass = getattr(importlib.import_module(module_name), class_name)
instance = MyClass()

Este código é válido em python ≥ 2.7 (incluindo python 3).

Régis B.
fonte
1
talvez eu não entendo a sua resposta, mas eu tenho usado importação para submódulos de importação: __import __ (+ submodule_name_string "módulo.")
Javier Novoa C.
O código a seguir resulta em um AttributeError: mod = __import__("os.path"); mod.joinenquanto o seguinte não:mod = importlib.import_module("os.path"); mod.join
Régis B.
oh eu vejo, você está certo, mas eu fiz o seguinte para obter o método os.path.join: getattr (sys.modules [ "os.path"], "join")
Javier Novoa C.
ha! e eu vejo que quando usando isso, então ' importação ' é XD desnecessário
Javier Novoa C.
2
A opção marcada como resposta não funcionou para mim (usando submódulos), no entanto, essa resposta funciona. Eu acho que deveria ser a resposta, pois é uma solução mais genérica para carregar dinamicamente os módulos.
Rkachach
14

Use getattrpara obter um atributo de um nome em uma sequência. Em outras palavras, obtenha a instância como

instance = getattr(modul, class_name)()
AFoglia
fonte
10

Snippet de copiar e colar:

import importlib
def str_to_class(module_name, class_name):
    """Return a class instance from a string reference"""
    try:
        module_ = importlib.import_module(module_name)
        try:
            class_ = getattr(module_, class_name)()
        except AttributeError:
            logging.error('Class does not exist')
    except ImportError:
        logging.error('Module does not exist')
    return class_ or None
Andrejs Cainikovs
fonte
5

Se você deseja que esta frase from foo.bar import foo2seja carregada dinamicamente, faça isso

foo = __import__("foo")
bar = getattr(foo,"bar")
foo2 = getattr(bar,"foo2")

instance = foo2()
Ahmad Muzakki
fonte
4

Pode-se simplesmente usar a pydoc.locatefunção.

from pydoc import locate
my_class = locate("module.submodule.myclass")
instance = my_class()
Xema
fonte
2

Eu não consegui chegar lá no meu caso de uso a partir dos exemplos acima, mas Ahmad me aproximou mais (obrigado). Para quem estiver lendo isso no futuro, aqui está o código que funcionou para mim.

def get_class(fully_qualified_path, module_name, class_name, *instantiation):
    """
    Returns an instantiated class for the given string descriptors
    :param fully_qualified_path: The path to the module eg("Utilities.Printer")
    :param module_name: The module name eg("Printer")
    :param class_name: The class name eg("ScreenPrinter")
    :param instantiation: Any fields required to instantiate the class
    :return: An instance of the class
    """
    p = __import__(fully_qualified_path)
    m = getattr(p, module_name)
    c = getattr(m, class_name)
    instance = c(*instantiation)
    return instance
SteveJ
fonte