给定一个Python类的字符串,例如my_package.my_module。MyClass,最好的加载方式是什么?

换句话说,我正在寻找一个等效的Class.forName()在Java,函数在Python。它需要工作在谷歌应用程序引擎。

最好是这样一个函数,它接受类的FQN作为字符串,并返回类的引用:

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

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

从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

上面的函数只在fromlist为空时才有必要。因此,适当的调用应该是这样的:

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

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)

import importlib

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

如果你不想滚动你自己的,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)."""

在谷歌应用引擎中有一个名为import_string的webapp2函数。更多信息请参见:https://webapp-improved.appspot.com/api/webapp2.html

So,

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

例如,这在webapp2中使用。可以使用处理程序或字符串的路由。


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

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

b是MyClass类的一个实例


如果你碰巧已经有一个你想要的类的实例,你可以使用'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()

如果你使用的是Django,你可以使用import_string。

是的,我知道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')

下面是分享一些我在__import__和importlib上发现的东西,同时试图解决这个问题。

我使用的是Python 3.7.3。

当我在a.b.c模块中找到d类时,

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

Python有一个内置库importlib来完成这项工作。:,如何动态访问模块方法和类方法,绕过包名作为参数。下面给出了一个例子。

模块1:

def get_scenario_data():
    return "module1 scenario data"


class Module1:

    def module1_function1(self):
        return "module1_function"

    def module1_function2(self):
        return "module2_function"

模块2:

def get_scenario_data():
    return "module2 scenario data"



class Module2:

    def module2_function1(self):
        return "module2_function1"

    def module2_function2(self):
        return "module2_function2"

ModuleTest:

将根据包名作为参数动态访问模块方法 将基于包名作为参数动态访问类方法。

ModuleTest

import importlib

module = importlib.import_module('pack1.nestedpack1.module1')
print(module.get_scenario_data())
modul1_cls_obj = getattr(module, 'Module1')()
print(modul1_cls_obj.module1_function1())
print(modul1_cls_obj.module1_function2())

module = importlib.import_module('pack1.nestedpack1.module2')
modul2_cls_obj = getattr(module, 'Module2')()
print(modul2_cls_obj.module2_function1())
print(modul2_cls_obj.module2_function2())
print(module.get_scenario_data())

结果

module1 scenario data
module1_function
module2_function
module2_function1
module2_function2
module2 scenario data

PyPI模块自动加载和导入

# PyPI imports
import pkg_resources, subprocess, sys

modules   = {'lxml.etree', 'pandas', 'screeninfo'}
required  = {m.split('.')[0] for m in modules}
installed = {pkg.key for pkg in pkg_resources.working_set}
missing   = required - installed

if missing:
    subprocess.check_call([sys.executable, '-m', 'pip', 'install', '--upgrade', 'pip'])
    subprocess.check_call([sys.executable, '-m', 'pip', 'install', *missing])

for module in set.union(required, modules):
    globals()[module] = __import__(module)

测试:

print(pandas.__version__)
print(lxml.etree.LXML_VERSION)

为现有的答案增加了一点复杂性....

根据用例,显式指定你想导入的类/方法的完整路径(例如package.subpackage.module…)可能有点不方便。在importlib之上,我们可以利用__init__.py使事情更加简洁。

假设我有一个python包,像这样:

├── modes
│   ├── __init__.py
│   ├── bar.py
│   ├── foo.py
│   ├── modes.py

Foo.py,比如说,有一些类/函数,我们想在程序的其他地方使用:

from modes.modes import Mode

class Foo(Mode):
    def __init__(self, *arg, **kwargs):
        super(Foo, self).__init__(*arg, **kwargs)
        
    def run(self):
        self.LOG.info(f"This is FOO!")

使用命令行参数,我可以传递一个与我想要运行的模式对应的参数。我希望能够像这样

def set_mode(mode):
    """  """
    import importlib
    module = importlib.import_module('modes.foo')
    getattr(module, mode)().run()

输出:

>> set_mode("Foo")
>> engine_logger:INFO - This is FOO!

这很好,但是我们真正想要得到的是:

def set_mode(mode):
    """  """
    import importlib
    module = importlib.import_module('modes')  # only import the package, not modules explicitely
    getattr(module, mode)().run()

这会引发一个错误:

>> set_mode("Foo")
>> AttributeError: module 'modes' has no attribute 'Foo'

但是,我们可以在/modes/__init__.py中添加以下内容:

from .foo import Foo
from .bar import Bar

然后,我们可以做:

>> set_mode("Foo")
>> engine_logger:INFO - This is FOO!

>> set_mode("Bar")
>> engine_logger:INFO - This is BAR!

在其他情况下,我们在init.py中导入的所有子模块/函数/类都可以直接在importlib.import_module(…)中找到,而不必从外部指定完整的路径。