Как динамически загружать класс Python

С учетом строки класса Python, например. my_package.my_module.MyClass, каков наилучший способ его загрузки?

Другими словами, я ищу эквивалентную Class.forName() в Java, функцию в Python. Он должен работать в Google App Engine.

Предпочтительно, это будет функция, которая принимает FQN класса как строку и возвращает ссылку на класс:

my_class = load_class('my_package.my_module.MyClass')
my_instance = my_class()

Ответ 1

В документации на python выполните нужную функцию:

def my_import(name):
    components = name.split('.')
    mod = __import__(components[0])
    for comp in components[1:]:
        mod = getattr(mod, comp)
    return mod

Причина, по которой простой __import__ не будет работать, заключается в том, что любой импорт чего-либо за первой точкой в ​​строке пакета является атрибутом импортируемого модуля. Таким образом, что-то вроде этого не будет работать:

__import__('foo.bar.baz.qux')

Вам нужно будет вызвать указанную выше функцию следующим образом:

my_import('foo.bar.baz.qux')

Или в случае вашего примера:

klass = my_import('my_package.my_module.my_class')
some_object = klass()

ИЗМЕНИТЬ. Я немного от этого отказался. Что вы в основном хотите сделать, так это:

from my_package.my_module import my_class

Вышеуказанная функция необходима только в том случае, если у вас есть пустой список. Таким образом, соответствующий вызов будет таким:

mod = __import__('my_package.my_module', fromlist=['my_class'])
klass = getattr(mod, 'my_class')

Ответ 2

Если вы не хотите откатывать свои собственные, в модуле pydoc есть функция, которая выполняет именно это:

from pydoc import locate
my_class = locate('my_package.my_module.MyClass')

Преимущество этого подхода в отношении остальных, перечисленных здесь, состоит в том, что locate найдет любой объект python на предоставленном пунктирном пути, а не только объект непосредственно внутри модуля. например my_package.my_module.MyClass.attr.

Если вам интересно, что их рецепт, здесь функция:

def locate(path, forceload=0):
    """Locate an object by name or dotted path, importing as necessary."""
    parts = [part for part in split(path, '.') if part]
    module, n = None, 0
    while n < len(parts):
        nextmodule = safeimport(join(parts[:n+1], '.'), forceload)
        if nextmodule: module, n = nextmodule, n + 1
        else: break
    if module:
        object = module
    else:
        object = __builtin__
    for part in parts[n:]:
        try:
            object = getattr(object, part)
        except AttributeError:
            return None
    return object

Он полагается на функцию pydoc.safeimport. Вот документы для этого:

"""Import a module; handle errors; return None if the module isn't found.

If the module *is* found but an exception occurs, it wrapped in an
ErrorDuringImport exception and reraised.  Unlike __import__, if a
package path is specified, the module at the end of the path is returned,
not the package at the beginning.  If the optional 'forceload' argument
is 1, we reload the module from disk (unless it a dynamic extension)."""

Ответ 3

import importlib

module = importlib.import_module('my_package.my_module')
my_class = getattr(module, 'MyClass')
my_instance = my_class()

Ответ 4

def import_class(cl):
    d = cl.rfind(".")
    classname = cl[d+1:len(cl)]
    m = __import__(cl[0:d], globals(), locals(), [classname])
    return getattr(m, classname)

Ответ 5

ОК, для меня это так, как это работает (я использую Python 2.7):

a = __import__('file_to_import', globals(), locals(), ['*'], -1)
b = a.MyClass()

Затем b является экземпляром класса 'MyClass'

Ответ 6

Если вы используете Django, вы можете использовать это. Да, я знаю, что OP не просил django, но я наткнулся на этот вопрос в поисках решения Django, не нашел его и поместил его здесь для следующего мальчика/девочки, который его ищет.

# It available for v1.7+
# https://github.com/django/django/blob/stable/1.7.x/django/utils/module_loading.py
from django.utils.module_loading import import_string

Klass = import_string('path.to.module.Klass')
func = import_string('path.to.module.func')
var = import_string('path.to.module.var')

Имейте в виду, если вы хотите импортировать то, что не имеет . , как re или argparse использовать:

re = __import__('re')

Ответ 7

Если у вас уже есть экземпляр нужного вам класса, вы можете использовать функцию 'type', чтобы извлечь его тип класса, и использовать его для создания нового экземпляра:

class Something(object):
    def __init__(self, name):
        self.name = name
    def display(self):
        print(self.name)

one = Something("one")
one.display()
cls = type(one)
two = cls("two")
two.display()

Ответ 8

Здесь я хочу поделиться тем, что я нашел в __import__ и importlib, пытаясь решить эту проблему.

Я использую Python 3.7.3.

Когда я пытаюсь добраться до класса d в модуле abc,

mod = __import__('a.b.c')

Переменная mod ссылается на верхнее пространство имен a.

Таким образом, чтобы добраться до класса d, мне нужно

mod = getattr(mod, 'b') #mod is now module b
mod = getattr(mod, 'c') #mod is now module c
mod = getattr(mod, 'd') #mod is now class d

Если мы попытаемся сделать

mod = __import__('a.b.c')
d = getattr(mod, 'd')

мы на самом деле пытаемся найти ad.

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

mod = importlib.import_module('a.b.c') #mod is module c
d = getattr(mod, 'd') #this is a.b.c.d

Ответ 9

module = __import__("my_package/my_module")
the_class = getattr(module, "MyClass")
obj = the_class()

Ответ 10

В Google App Engine есть функция webapp2, называемая import_string. Для получения дополнительной информации см. Здесь: https://webapp-improved.appspot.com/api/webapp2.html

Итак,

import webapp2
my_class = webapp2.import_string('my_package.my_module.MyClass')

Например, это используется в webapp2.Route, где вы можете использовать обработчик или строку.