Dynamiczna instancja z nazwy ciągu klasy w module importowanym dynamicznie?

179

W Pythonie muszę utworzyć instancję określonej klasy, znając jej nazwę w ciągu, ale ta klasa „żyje” w dynamicznie importowanym module. Oto przykład:

skrypt klasy ładującej:

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

jakiś dynamicznie ładowany skrypt skryptu:

class myName:
  # etc...

Korzystam z tego układu, aby każdy moduł ładowany dynamicznie był używany przez klasę modułu ładującego zgodnie z pewnymi predefiniowanymi zachowaniami w modułach ładowanych dynamicznie ...

Javier Novoa C.
źródło

Odpowiedzi:

251

Możesz użyć getattr

getattr(module, class_name)

aby uzyskać dostęp do klasy. Pełniejszy kod:

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

Jak wspomniano poniżej , możemy użyć importlib

import importlib
module = importlib.import_module(module_name)
class_ = getattr(module, class_name)
instance = class_()
Sven Marnach
źródło
3
module = __import__(module, fromlist=[name])działało tylko dla mnie.
umpirsky
16
Jeśli ktoś ma problem ze wspomnianą wyżej metodą importu Sven, mój kod działał lepiej, stosując następującą metodę zamiast importlib.import_module . Może być użyty jak: moduł = importlib.import_module (nazwa_modułu)
jpennell,
@jpennell powinieneś zamieścić to jako odpowiedź, często bardziej użyteczne jest bezpośrednie użycie ciągu zwróconego przezobj.__module__
Anentropic
importlib.import_modulew razie potrzeby załaduje plik .py do pliku pyc, a także obsłuży kompletny moduł.nazwa.pathing.to.get.to. klasy. __import__nie zrobi żadnej z tych rzeczy w środowisku django (nie testowane poza tym)
James
122

tl; dr

Zaimportuj moduł główny importlib.import_modulei załaduj klasę według jej nazwy, używając getattrfunkcji:

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

wyjaśnienia

Prawdopodobnie nie chcesz używać __import__do dynamicznego importowania modułu według nazwy, ponieważ nie pozwala on na importowanie submodułów:

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

Oto, co mówi dokument Python __import__:

Uwaga: Jest to zaawansowana funkcja, która nie jest potrzebna w codziennym programowaniu w języku Python, w przeciwieństwie do importlib.import_module ().

Zamiast tego użyj standardowego importlibmodułu, aby dynamicznie zaimportować moduł według nazwy. Za pomocą getattrmożesz utworzyć instancję klasy według jej nazwy:

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

Możesz także napisać:

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

Ten kod jest poprawny w pythonie ≥ 2.7 (w tym python 3).

Régis B.
źródło
1
może nie rozumiem twojej odpowiedzi, ale użyłem importu, aby zaimportować submoduły: __import __ („moduł” + string_nazwa_modułu)
Javier Novoa C.
Następujący kod powoduje AttributeError: mod = __import__("os.path"); mod.joinpodczas gdy następujący nie:mod = importlib.import_module("os.path"); mod.join
Régis B.
och, rozumiem, masz rację, ale zrobiłem następujące, aby uzyskać metodę os.path.join: getattr (sys.modules [„os.path”], „join”)
Javier Novoa C.
ha! i widzę, że podczas korzystania z tego „ importowanie ” jest niepotrzebne XD
Javier Novoa C.
2
Opcja oznaczona jako odpowiedź nie działała dla mnie (przy użyciu podmodułów), jednak ta odpowiedź działa. Myślę, że powinna to być odpowiedź, ponieważ jest to bardziej ogólne rozwiązanie do dynamicznego ładowania modułów.
rkachach
14

Użyj, getattraby uzyskać atrybut z nazwy w ciągu. Innymi słowy, pobierz instancję jako

instance = getattr(modul, class_name)()
AFoglia
źródło
10

Skopiuj i wklej fragment kodu:

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
źródło
5

Jeśli chcesz, aby to zdanie from foo.bar import foo2było ładowane dynamicznie, powinieneś to zrobić

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

instance = foo2()
Ahmad Muzakki
źródło
4

Można po prostu użyć tej pydoc.locatefunkcji.

from pydoc import locate
my_class = locate("module.submodule.myclass")
instance = my_class()
Xema
źródło
2

Nie mogłem się do końca dostać w moim przypadku użycia z powyższych przykładów, ale Ahmad dał mi najbliższą (dziękuję). Dla tych, którzy czytają to w przyszłości, oto kod, który działał dla mnie.

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
źródło