Динамическое создание из строкового имени класса в динамически импортированном модуле?

180

В python мне нужно создать экземпляр определенного класса, зная его имя в строке, но этот класс «живет» в динамически импортированном модуле. Вот пример:

скрипт класса загрузчика:

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

скрипт некоторых динамически загружаемых модулей:

class myName:
  # etc...

Я использую это расположение, чтобы любой динамически загружаемый модуль использовался классом загрузчика, следуя определенным предопределенным поведениям в dyn -loaded-modules ...

Хавьер Новоа С.
источник

Ответы:

251

Вы можете использовать getattr

getattr(module, class_name)

чтобы получить доступ к классу. Более полный код:

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

Как упомянуто ниже , мы можем использовать importlib

import importlib
module = importlib.import_module(module_name)
class_ = getattr(module, class_name)
instance = class_()
Свен Марнах
источник
3
module = __import__(module, fromlist=[name])работал только для меня.
umpirsky
16
Если у кого-то возникли проблемы с упомянутым выше методом импорта Sven, я обнаружил, что мой код работает лучше, используя следующий метод вместо importlib.import_module . Может использоваться как: module = importlib.import_module (module_name)
jpennell
@jpennell, вы должны опубликовать это как ответ, часто более полезно иметь возможность напрямую использовать строку, возвращаемуюobj.__module__
Anentropic
importlib.import_moduleпри необходимости загрузит файл .py в pyc, а также обработает полный класс module.name.pathing.to.get.to.the. __import__не будет делать ни одну из этих вещей в среде django (не проверенной вне этого)
Джеймс
124

ТЛ; др

Импортируйте корневой модуль с помощью importlib.import_moduleи загрузите класс по его имени, используя getattrфункцию:

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

объяснения

Вы, вероятно, не хотите использовать __import__для динамического импорта модуля по имени, так как он не позволяет импортировать подмодули:

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

Вот что говорит о Python Doc __import__:

Примечание. Это расширенная функция, которая не нужна в повседневном программировании на Python, в отличие от importlib.import_module ().

Вместо этого используйте стандартный importlibмодуль для динамического импорта модуля по имени. С getattrвы можете затем создать экземпляр класса по имени:

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

Вы также можете написать:

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

Этот код действителен в Python ≥ 2.7 (включая Python 3).

Режис Б.
источник
1
возможно, я не понимаю ваш ответ, но я использовал импорт для импорта подмодулей: __import __ ("module." + submodule_name_string)
Хавьер Новоа С.
Следующий код приводит к ошибке AttributeError: в mod = __import__("os.path"); mod.joinто время как следующий не делает:mod = importlib.import_module("os.path"); mod.join
Régis B.
о, я понимаю, вы правы, но я сделал следующее, чтобы получить метод os.path.join: getattr (sys.modules ["os.path"], "join")
Хавьер Новоа С.
ха! и я вижу, что при использовании этого, « импорт » не нужен XD
Хавьер Новоа С.
2
Опция, помеченная как ответ, не работает для меня (с использованием подмодулей), однако этот ответ работает. Я думаю, что это должен быть ответ, так как это более общее решение для динамической загрузки модулей.
ркачач
14

Используйте getattrдля получения атрибута из имени в строке. Другими словами, получить экземпляр как

instance = getattr(modul, class_name)()
AFoglia
источник
10

Скопировать-вставить фрагмент:

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

Если вы хотите, чтобы это предложение from foo.bar import foo2загружалось динамически, вы должны сделать это

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

instance = foo2()
Ахмад Музакки
источник
4

Можно просто использовать pydoc.locateфункцию.

from pydoc import locate
my_class = locate("module.submodule.myclass")
instance = my_class()
Xema
источник
2

Я не вполне смог добраться до моего варианта использования из приведенных выше примеров, но Ахмад подобрал меня ближе всех (спасибо). Для тех, кто читает это в будущем, вот код, который работал для меня.

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
источник