Як динамічно завантажувати клас 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()

Мені потрібно мати можливість призначити посилання класу і на змінну.
песі

1
Це , як видається дублікат: stackoverflow.com/questions/452969 / ...
cdleary

Ви маєте рацію, це дублікат, дякую, що знайшли його
песи

1
Мені ніколи не потрібно було завантажувати такий клас у Python. Ви знаєте, де знаходиться модуль, то чому б просто не завантажувати модуль, а потім використовувати його класи, як хоче Python, його набагато простіше
Адам Спенс,

22
Якщо вам ніколи цього не потрібно було, ви ще не написали достатньо цікавих програм. Продовжуйте займатися.
Джон Тайрі

Відповіді:


194

З документації на 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')

Я спробував my_import ('my_package.my_module.my_class'), але не знайшов жодного модуля my_class, що має сенс, оскільки це клас, а не модуль. Якби я не міг скористатись gettattr, щоб отримати клас після дзвінка на my_import
песи

Це дивно. Все, що минуло першу крапку, називається за допомогою getattr. Не повинно бути різниці.
Джейсон Бейкер

Дякую, я думаю, що це найкращий спосіб. Тепер мені потрібен лише найкращий спосіб розділити рядок 'my_pakcage.my_module.my_class' на mod_name, klass_name, але, мабуть, я можу це зрозуміти :)
pjesi

2
Документація python (на код) імпорту говорить про використання importlib. так повинен оформити відповідь Адам Спенс
Наоко

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)."""

1
Я відповів на цю відповідь. BTW, ось код, який також має Safeimport, оскільки дивно імпортувати pydoc саме для цього: github.com/python/cpython/blob/…
brianray

Я відповів і на цю відповідь, це найкраще з усіх відповідних відповідей.
Сундінг Вей

Це, здається, може також правильно обробляти 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-Benny and Spence! ;)
dKen

Чудово! Це навіть частина стандартної бібліотеки від 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

17

Якщо ви використовуєте Django, ви можете використовувати це. Так, я знаю, що ОП не просив джанго, але я наткнувся на це питання, шукаючи рішення Джанго, не знайшов його, і поставив його тут для наступного хлопця / галя, який його шукає.

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

5

Ось, щоб поділитися тим, що я знайшов, __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"


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

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коли ви можете використовувати обробник або рядок.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.