Instanciation dynamique à partir du nom de chaîne d'une classe dans un module importé dynamiquement?

180

En python, je dois instancier une certaine classe, connaissant son nom dans une chaîne, mais cette classe «vit» dans un module importé dynamiquement. Un exemple suit:

script de classe chargeur:

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

un script de module chargé dynamiquement:

class myName:
  # etc...

J'utilise cet arrangement pour faire en sorte que n'importe quel module chargé dynamiquement soit utilisé par la classe loader suivant certains comportements prédéfinis dans les modules chargés dynamiquement ...

Javier Novoa C.
la source

Réponses:

251

Vous pouvez utiliser getattr

getattr(module, class_name)

pour accéder à la classe. Code plus complet:

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

Comme mentionné ci - dessous , nous pouvons utiliser importlib

import importlib
module = importlib.import_module(module_name)
class_ = getattr(module, class_name)
instance = class_()
Sven Marnach
la source
3
module = __import__(module, fromlist=[name])a seulement fonctionné pour moi.
umpirsky
16
Si quelqu'un a des problèmes avec la méthode d'importation Sven mentionnée ci-dessus, j'ai trouvé que mon code fonctionnait mieux en utilisant la méthode suivante à la place importlib.import_module . Peut être utilisé comme: module = importlib.import_module (module_name)
jpennell
@jpennell vous devriez poster cela comme réponse, il est souvent plus utile de pouvoir utiliser directement la chaîne retournée parobj.__module__
Anentropic
importlib.import_modulechargera le fichier .py dans un pyc si nécessaire et gérera le module.name.pathing.to.get.to. the class. __import__ne fera ni l'une ni l'autre de ces choses, dans un environnement django (non testé en dehors de cela)
James
123

tl; dr

Importez le module racine avec importlib.import_moduleet chargez la classe par son nom en utilisant la getattrfonction:

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

explications

Vous ne souhaitez probablement pas utiliser __import__pour importer dynamiquement un module par nom, car cela ne vous permet pas d'importer des sous-modules:

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

Voici ce que dit la documentation python __import__:

Remarque: il s'agit d'une fonction avancée qui n'est pas nécessaire dans la programmation Python quotidienne, contrairement à importlib.import_module ().

À la place, utilisez le importlibmodule standard pour importer dynamiquement un module par nom. Avec getattrvous pouvez alors instancier une classe par son nom:

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

Vous pouvez également écrire:

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

Ce code est valide en python ≥ 2.7 (y compris python 3).

Régis B.
la source
1
peut-être que je ne comprends pas votre réponse, mais j'ai utilisé l' importation pour importer des sous-modules: __import __ ("module." + submodule_name_string)
Javier Novoa C.
Le code suivant entraîne une erreur AttributeError: mod = __import__("os.path"); mod.joincontrairement à ce qui suit:mod = importlib.import_module("os.path"); mod.join
Régis B.
oh je vois, vous avez raison mais j'ai fait ce qui suit pour obtenir la méthode os.path.join: getattr (sys.modules ["os.path"], "join")
Javier Novoa C.
Ha! et je vois que lors de l'utilisation de cela, alors « importer » est inutile XD
Javier Novoa C.
2
L'option marquée comme réponse n'a pas fonctionné pour moi (en utilisant des sous-modules), mais cette réponse fonctionne. Je pense que cela devrait être la réponse car c'est une solution plus générique pour le chargement dynamique des modules.
rkachach
14

Utilisez getattrpour obtenir un attribut à partir d'un nom dans une chaîne. En d'autres termes, obtenez l'instance comme

instance = getattr(modul, class_name)()
AFoglie
la source
10

Copier-coller un extrait:

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
la source
5

Si vous voulez que cette phrase from foo.bar import foo2soit chargée dynamiquement, vous devez le faire

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

instance = foo2()
Ahmad Muzakki
la source
4

On peut simplement utiliser la pydoc.locatefonction.

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

Je ne pouvais pas vraiment y arriver dans mon cas d'utilisation à partir des exemples ci-dessus, mais Ahmad m'a le plus proche (merci). Pour ceux qui liront ceci à l'avenir, voici le code qui a fonctionné pour moi.

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
la source