Dynamische Instanziierung aus dem Zeichenfolgennamen einer Klasse in einem dynamisch importierten Modul?

179

In Python muss ich eine bestimmte Klasse instanziieren und ihren Namen in einer Zeichenfolge kennen, aber diese Klasse "lebt" in einem dynamisch importierten Modul. Ein Beispiel folgt:

Loader-Class-Skript:

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

Skript für ein dynamisch geladenes Modul:

class myName:
  # etc...

Ich benutze diese Anordnung, um jedes dynamisch geladene Modul so zu gestalten, dass es von der Loader-Klasse nach bestimmten vordefinierten Verhaltensweisen in den dyn-geladenen Modulen verwendet wird ...

Javier Novoa C.
quelle

Antworten:

248

Sie können getattr verwenden

getattr(module, class_name)

um auf die Klasse zuzugreifen. Vollständigerer Code:

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

Wie unten erwähnt , können wir importlib verwenden

import importlib
module = importlib.import_module(module_name)
class_ = getattr(module, class_name)
instance = class_()
Sven Marnach
quelle
3
module = __import__(module, fromlist=[name])hat nur für mich gearbeitet.
Umpirsky
16
Wenn jemand Probleme mit der oben erwähnten Importmethode Sven hat, habe ich festgestellt, dass mein Code mit der folgenden Methode besser funktioniert, anstatt mit importlib.import_module . Kann verwendet werden wie: module = importlib.import_module (module_name)
jpennell
@jpennell Sie sollten das als Antwort posten, es ist oft nützlicher, in der Lage zu sein, die vonobj.__module__
Anentropic
importlib.import_modulelädt die .py-Datei bei Bedarf in einen pyc und verarbeitet den vollständigen Modulnamen.Pfad.zu.get.zu.der Klasse. __import__wird keines dieser Dinge tun, in einer Django-Umgebung (nicht außerhalb davon getestet)
James
121

tl; dr

Importieren Sie das Root-Modul mit importlib.import_moduleund laden Sie die Klasse anhand ihres Namens mithilfe der folgenden getattrFunktion:

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

Erklärungen

Sie möchten wahrscheinlich kein __import__Modul dynamisch nach Namen importieren, da Sie damit keine Submodule importieren können:

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

Hier ist , was der Python - doc sagt über __import__:

Hinweis: Dies ist eine erweiterte Funktion, die im Gegensatz zur importlib.import_module () in der täglichen Python-Programmierung nicht benötigt wird.

Verwenden Sie stattdessen das Standardmodul importlib, um ein Modul dynamisch nach Namen zu importieren. Mit können getattrSie dann eine Klasse anhand ihres Namens instanziieren:

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

Sie könnten auch schreiben:

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

Dieser Code ist in Python ≥ 2.7 (einschließlich Python 3) gültig.

Régis B.
quelle
1
Vielleicht verstehe ich Ihre Antwort nicht, aber ich habe Import verwendet , um Submodule zu importieren: __import __ ("module." + submodule_name_string)
Javier Novoa C.
Der folgende Code führt zu einem AttributeError: mod = __import__("os.path"); mod.joinwährend der folgende nicht:mod = importlib.import_module("os.path"); mod.join
Régis B.
Oh, ich verstehe, Sie haben Recht, aber ich habe Folgendes getan, um die Methode os.path.join zu erhalten: getattr (sys.modules ["os.path"], "join")
Javier Novoa C.
Ha! und ich sehe, dass, wenn Sie das verwenden, dann ' Import ' unnötig ist XD
Javier Novoa C.
2
Die als Antwort gekennzeichnete Option hat bei mir nicht funktioniert (unter Verwendung von Submodulen), diese Antwort funktioniert jedoch. Ich denke, es sollte die Antwort sein, da es eine allgemeinere Lösung für das dynamische Laden von Modulen ist.
Rkachach
14

Verwenden Sie getattrdiese Option , um ein Attribut aus einem Namen in einer Zeichenfolge abzurufen. Mit anderen Worten, erhalten Sie die Instanz als

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

Kopieren-Einfügen-Snippet:

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

Wenn Sie möchten, dass dieser Satz from foo.bar import foo2dynamisch geladen wird, sollten Sie dies tun

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

instance = foo2()
Ahmad Muzakki
quelle
4

Man kann einfach die pydoc.locateFunktion benutzen .

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

Ich konnte in meinem Anwendungsfall aus den obigen Beispielen nicht ganz dahin gelangen, aber Ahmad hat mich am nächsten gebracht (danke). Für diejenigen, die dies in Zukunft lesen, ist hier der Code, der für mich funktioniert hat.

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
quelle