¿Instanciación dinámica del nombre de cadena de una clase en un módulo importado dinámicamente?

179

En python, tengo que crear una instancia de cierta clase, sabiendo su nombre en una cadena, pero esta clase 'vive' en un módulo importado dinámicamente. A continuación se muestra un ejemplo:

script de clase de cargador:

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 algún módulo cargado dinámicamente:

class myName:
  # etc...

Utilizo esta disposición para hacer que cualquier módulo cargado dinámicamente sea utilizado por la clase loader siguiendo ciertos comportamientos predefinidos en los módulos cargados dinámicamente ...

Javier Novoa C.
fuente

Respuestas:

251

Puedes usar getattr

getattr(module, class_name)

para acceder a la clase. Código más completo:

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

Como se menciona a continuación , podemos usar importlib

import importlib
module = importlib.import_module(module_name)
class_ = getattr(module, class_name)
instance = class_()
Sven Marnach
fuente
3
module = __import__(module, fromlist=[name])Solo funcionó para mí.
Umpirsky
16
Si alguien tiene problemas con el método de importación que Sven mencionó anteriormente, descubrí que mi código funcionó mejor usando el siguiente método en su lugar importlib.import_module . Se puede usar como: module = importlib.import_module (module_name)
jpennell
@jpennell debe publicar eso como respuesta, a menudo es más útil poder usar directamente la cadena devuelta porobj.__module__
Anentropic
importlib.import_modulecargará el archivo .py en una pyc si es necesario, así como manejará el módulo completo.nombre.pathing.to.get.to.la clase. __import__no hará ninguna de estas cosas, en un entorno django (no probado fuera de esto)
James
122

tl; dr

Importe el módulo raíz importlib.import_moduley cargue la clase por su nombre usando la getattrfunción:

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

explicaciones

Probablemente no desee utilizar __import__para importar dinámicamente un módulo por nombre, ya que no le permite importar submó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'

Esto es lo que dice el documento de Python __import__:

Nota: Esta es una función avanzada que no es necesaria en la programación diaria de Python, a diferencia de importlib.import_module ().

En su lugar, use el importlibmódulo estándar para importar dinámicamente un módulo por nombre. Con getattrusted puede crear una instancia de una clase por su nombre:

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

También podrías escribir:

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 es válido en python ≥ 2.7 (incluido python 3).

Régis B.
fuente
1
tal vez no entiendo su respuesta, pero he usado import para importar submódulos: __import __ ("module". + submodule_name_string)
Javier Novoa C.
El siguiente código da como resultado un AttributeError: mod = __import__("os.path"); mod.joinmientras que el siguiente no:mod = importlib.import_module("os.path"); mod.join
Régis B.
oh ya veo, tienes razón pero he hecho lo siguiente para obtener el método os.path.join: getattr (sys.modules ["os.path"], "join")
Javier Novoa C.
¡decir ah! y veo que cuando uso eso, entonces ' importar ' es innecesario XD
Javier Novoa C.
2
La opción marcada como respuesta no funcionó para mí (usando submódulos), sin embargo, esta respuesta sí. Creo que debería ser la respuesta, ya que es una solución más genérica para cargar módulos dinámicamente.
rkachach
14

Use getattrpara obtener un atributo de un nombre en una cadena. En otras palabras, obtenga la instancia como

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

Fragmento de copiar y pegar:

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
fuente
5

Si desea que esta oración from foo.bar import foo2se cargue dinámicamente, debe hacer esto

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

instance = foo2()
Ahmad Muzakki
fuente
4

Uno simplemente puede usar la pydoc.locatefunción.

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

No pude llegar allí en mi caso de uso de los ejemplos anteriores, pero Ahmad me consiguió el más cercano (gracias). Para aquellos que lean esto en el futuro, aquí está el código que funcionó para mí.

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
fuente