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

167

Учитывая строку класса 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()
pjesi
источник
Мне нужно иметь возможность назначить ссылку на класс переменной.
pjesi
1
Похоже, это дубликат: stackoverflow.com/questions/452969/…
cdleary,
Вы правы, это дубликат, спасибо, что нашли его
pjesi
1
Мне никогда не нужно было загружать такой класс в Python. Вы знаете, где находится модуль, так почему бы просто не загрузить модуль, а затем использовать его классы, как того хочет Python, гораздо проще
Адам Спенс
22
Если вам никогда не приходилось делать это, значит, вы еще не написали достаточно интересных программ. Продолжай практиковаться.
Джон Тайри

Ответы:

194

Из документации по питону, вот функция, которую вы хотите:

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')
Джейсон Бейкер
источник
Я попытался my_import ('my_package.my_module.my_class'), но не нашел модуль my_class, что имеет смысл, так как это класс, а не модуль. Как бы то ни было, если я смогу использовать gettattr для получения класса после вызова my_import
pjesi
Странно. Все, что за первой точкой, вызывается с помощью getattr. Там не должно быть никакой разницы.
Джейсон Бейкер
Спасибо, я думаю, что это лучший способ. Теперь мне нужен только лучший способ разбить строку «my_pakcage.my_module.my_class» на mod_name, klass_name, но, думаю, я смогу это выяснить :)
pjesi
2
Документация Python (о коде) импорта говорит, что использовать importlib. так что стоит проверить ответ Адама Спенса
naoko
1
Ссылка @naoko ссылается на: docs.python.org/3/library/importlib.html#importlib.__import__
Ноэль Эванс
125

Если вы не хотите бросать свои собственные, в 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's 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's a dynamic extension)."""
chadrik
источник
1
Я проголосовал за этот ответ. Кстати, вот код, который также имеет safeimport, так как кажется странным импортировать pydoc только для этого: github.com/python/cpython/blob/…
brianray,
Я тоже проголосовал за этот ответ, это лучший из всех соответствующих ответов.
Sunding Wei
Похоже, что это может правильно обрабатывать qualname(объект не в верхней части пространства имен модуля).
Мистер Мияги
да, вы можете сделатьlocate('my_package.my_module.MyClass.attr.method.etc')
чадрик
109
import importlib

module = importlib.import_module('my_package.my_module')
my_class = getattr(module, 'MyClass')
my_instance = my_class()
Адам Спенс
источник
9
как только вы импортировали модуль динамически, у вас есть доступ к классу через модуль
Адам Спенс
Просто отредактировал мой ответ, чтобы быть более кратким. Это лучший способ загрузить класс в Python.
Адам Спенс
BBB-Бенни и Спенс! ;)
17
Большой! Это даже часть стандартной библиотеки от 2.7 и выше.
Apteryx,
Это правильный способ доступа к модулю / классу. Документы утверждают это здесь: docs.python.org/3/library/importlib.html#importlib.__import__
Ноэль Эванс
29
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
Это чистое решение! Вы можете рассмотреть возможность использования:(modulename, classname) = cl.rsplit('.', 2)
vdboor
Это здорово) Я создал пакет putils с различными утилитами, также импортировал туда классы. Если вы хотите, вы можете использовать его из этого пакета.
Стэн
4
@vdboor rsplit('.', 1)?
Carles Barrobés
Мне удалось пройти {} вместо глобалов / местных жителей, и это все еще работает отлично
valtron
18

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

# It's 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')
Хавьер Буззи
источник
6

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

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

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

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')

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

При использовании 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
Ричард Вонг
источник
1

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

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

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

lfvv
источник
0

Если у вас уже есть экземпляр нужного вам класса, вы можете использовать функцию '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()
Джейсон Де Морроу
источник
-2
module = __import__("my_package/my_module")
the_class = getattr(module, "MyClass")
obj = the_class()
oefe
источник
5
Обратите внимание, что это работает из-за ошибки в функции импорта . Пути к файлам не должны использоваться в функции импорта и не будут работать в Python 2.6 и выше: docs.python.org/whatsnew/2.6.html#porting-to-python-2-6
Джейсон Бейкер,
-2

В 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где вы можете использовать либо обработчик, либо строку.

ph0eb0s
источник