如何动态加载Python类

问题:如何动态加载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()

Given a string of a Python class, e.g. my_package.my_module.MyClass, what is the best possible way to load it?

In other words I am looking for a equivalent Class.forName() in Java, function in Python. It needs to work on Google App Engine.

Preferably this would be a function that accepts the FQN of the class as a string, and returns a reference to the class:

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

回答 0

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

From the python documentation, here’s the function you want:

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

The reason a simple __import__ won’t work is because any import of anything past the first dot in a package string is an attribute of the module you’re importing. Thus, something like this won’t work:

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

You’d have to call the above function like so:

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

Or in the case of your example:

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

EDIT: I was a bit off on this. What you’re basically wanting to do is this:

from my_package.my_module import my_class

The above function is only necessary if you have a empty fromlist. Thus, the appropriate call would be like this:

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

回答 1

如果您不想自己动手,则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)."""

If you don’t want to roll your own, there is a function available in the pydoc module that does exactly this:

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

The advantage of this approach over the others listed here is that locate will find any python object at the provided dotted path, not just an object directly within a module. e.g. my_package.my_module.MyClass.attr.

If you’re curious what their recipe is, here’s the function:

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

It relies on pydoc.safeimport function. Here are the docs for that:

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

回答 2

import importlib

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

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

回答 3

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

回答 4

如果您使用的是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')

If you’re using Django you can use this. Yes i’m aware OP did not ask for django, but i ran across this question looking for a Django solution, didn’t find one, and put it here for the next boy/gal that looks for it.

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

Keep in mind, if you want to import something that doesn’t have a ., like re or argparse use:

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

Here is to share something I found on __import__ and importlib while trying to solve this problem.

I am using Python 3.7.3.

When I try to get to the class d in module a.b.c,

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

The mod variable refer to the top namespace a.

So to get to the class d, I need to

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

If we try to do

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

we are actually trying to look for a.d.

When using importlib, I suppose the library has done the recursive getattr for us. So, when we use importlib.import_module, we actually get a handle on the deepest module.

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

回答 6

好的,对我来说,这就是它的工作方式(我正在使用Python 2.7):

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

然后,b是类’MyClass’的实例

OK, for me that is the way it worked (I am using Python 2.7):

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

Then, b is an instance of class ‘MyClass’


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

If you happen to already have an instance of your desired class, you can use the ‘type’ function to extract its class type and use this to construct a new instance:

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

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

回答 9

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

In Google App Engine there is a webapp2 function called import_string. For more info see here:https://webapp-improved.appspot.com/api/webapp2.html

So,

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

For example this is used in the webapp2.Route where you can either use a handler or a string.