如何动态加载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

1
这似乎与以下内容重复:stackoverflow.com/questions/452969/…–
cdleary

您说得对,它是重复的,感谢您找到它
pjesi

1
我从来不需要在Python中加载这样的类。你知道在哪里模块是这样,为什么不只是加载模块,然后用它的类象Python要你,它的简单得多
亚当斯彭斯

22
如果您从来不需要这样做,那么您还没有编写足够有趣的程序。保持练习。
约翰·泰瑞

Answers:


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,这很有意义,因为它是一个类而不是一个模块。我如何才能在调用my_import之后使用gettattr来获取该类
pjesi

真奇怪 使用getattr调用第一个点之后的所有内容。不应有任何区别。
杰森·贝克

谢谢,我认为这是最好的方法。现在,我只需要最好的方法将字符串“ my_pakcage.my_module.my_class”拆分为mod_name,klass_name,但我想我可以弄清楚这一点:)
pjesi

2
导入的 python文档(在代码上)说要使用importlib。所以应该由亚当·斯彭斯(Adam Spence)结帐答案
naoko


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
我赞成这个答案。顺便说一句,这是也具有安全导入的代码,因为仅出于此目的导入pydoc似乎很奇怪:github.com/python/cpython/blob/…– brianray 2015
6

我也赞成这个答案,这是所有相关答案中最好的。
Sunding Wei

这似乎也能够正确处理qualname(对象不在模块名称空间的顶部)。
MiyaMiyagi '18

是的,您可以做locate('my_package.my_module.MyClass.attr.method.etc')
chadrik '18

109
import importlib

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

9
动态导入模块后,您就可以通过该模块访问该类
Adam Spence 2013年

刚刚编辑了我的答案,使其更加简洁。这是在Python中加载类的最佳方法。
亚当·斯彭斯

BBB-Benny和Spence!;)
dKen

大!它甚至是2.7及更高版本的标准库的一部分。
Apteryx

这是访问模块/类的正确方法。docs在这里指出
Noel Evans

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 2012年

太好了)我用不同的utils创建了putils包,也在那里导入了类。如果需要,可以从该软件包中使用它。
斯坦(Stan)2012年

4
@vdboor rsplit('.', 1)
CarlesBarrobés'13

我设法通过{}而不是globals / locals,它仍然可以正常工作
valtron 2014年

17

如果您使用的是Django,则可以使用它。是的,我知道OP并没有要求使用django,但是我遇到了一个问题,寻找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

在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在可以使用处理程序或字符串的地方使用它。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.