Istanziazione dinamica dal nome stringa di una classe nel modulo importato dinamicamente?


179

In Python, devo istanziare una certa classe, conoscendo il suo nome in una stringa, ma questa classe "vive" in un modulo importato dinamicamente. Un esempio segue:

script di classe caricatore:

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 con modulo caricato in modo dinamico:

class myName:
  # etc...

Uso questa disposizione per creare qualsiasi modulo caricato dinamicamente da utilizzare dalla classe loader seguendo determinati comportamenti predefiniti nei moduli caricati din ...

Risposte:


251

Puoi usare getattr

getattr(module, class_name)

per accedere alla classe. Codice più completo:

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

Come menzionato di seguito , possiamo usare importlib

import importlib
module = importlib.import_module(module_name)
class_ = getattr(module, class_name)
instance = class_()

3
module = __import__(module, fromlist=[name])ha funzionato solo per me.
umpirsky,

16
Se qualcuno ha problemi con il metodo di importazione menzionato sopra, ho scoperto che il mio codice funzionava meglio usando il seguente metodo invece importlib.import_module . Può essere usato come: module = importlib.import_module (module_name)
jpennell

@jpennell dovresti postarlo come risposta, spesso è più utile poter usare direttamente la stringa restituita daobj.__module__
Anentropic

importlib.import_modulecaricherà il file .py in un pyc se necessario e gestirà il modulo completo.name.pathing.to.get.to.la classe. __import__non farà nessuna di queste cose, in un ambiente django (non testato al di fuori di questo)
James

122

tl; dr

Importa il modulo root con importlib.import_modulee carica la classe con il suo nome usando la getattrfunzione:

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

spiegazioni

Probabilmente non vuoi usare __import__per importare dinamicamente un modulo per nome, in quanto non ti consente di importare sottomoduli:

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

Ecco cosa dice il documento su Python __import__:

Nota: questa è una funzione avanzata che non è necessaria nella programmazione quotidiana di Python, a differenza di importlib.import_module ().

Utilizzare invece il importlibmodulo standard per importare dinamicamente un modulo per nome. Con getattrte puoi quindi istanziare una classe con il suo nome:

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

Puoi anche scrivere:

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

Questo codice è valido in Python ≥ 2.7 (incluso Python 3).


1
forse non capisco la tua risposta, ma ho usato l' importazione per importare i sottomoduli: __import __ ("module." + submodule_name_string)
Javier Novoa C.

Il seguente codice genera un AttributeError: mod = __import__("os.path"); mod.joinmentre il seguente non lo fa:mod = importlib.import_module("os.path"); mod.join
Régis B.

oh vedo, hai ragione ma ho fatto quanto segue per ottenere il metodo os.path.join: getattr (sys.modules ["os.path"], "join")
Javier Novoa C.

ah! e vedo che quando lo si utilizza, quindi ' import ' non è necessario XD
Javier Novoa C.

2
L'opzione contrassegnata come risposta non ha funzionato per me (utilizzando i sottomoduli), tuttavia questa risposta funziona. Penso che dovrebbe essere la risposta poiché è la soluzione più generica per il caricamento dinamico dei moduli.
rkachach,

14

Utilizzare getattrper ottenere un attributo da un nome in una stringa. In altre parole, ottieni l'istanza come

instance = getattr(modul, class_name)()

10

Copia e incolla frammento:

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

5

Se vuoi che questa frase from foo.bar import foo2venga caricata in modo dinamico, dovresti farlo

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

instance = foo2()

4

Si può semplicemente usare la pydoc.locatefunzione.

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

2

Non ho potuto arrivarci nel mio caso d'uso dagli esempi sopra, ma Ahmad mi ha preso il più vicino (grazie). Per coloro che leggeranno questo in futuro, ecco il codice che ha funzionato per me.

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.