Python是否有类似于Java Class.forName()的等效方法?

111
我需要使用一个字符串参数,并在Python中创建一个名为该字符串的类的对象。在Java中,我会使用Class.forName().newInstance()。Python中是否有相当的方法?
感谢回复。为了回答那些想知道我在做什么的人:我希望将命令行参数用作类名,并实例化它。我实际上是在编写Jython并实例化Java类,因此问题具有Java特性。getattr()非常好用。非常感谢。

请看 https://dev59.com/0Wgv5IYBdhLWcg3wVfV6#10675081 有一个使用 importlib.import 的例子,这个函数已经从 Python 3 回溯到了 2.7 版本(https://docs.python.org/2/library/importlib.html)。 - Pat
我认为这里所有复杂的技巧都直接与被接受的答案中“Python中的反射比Java更容易且更灵活”的说法相矛盾。我认为正确的答案应该是“不,它并没有,所以你必须自己编写一个”(这基本上就是@hasen的答案所说的)。 - Ken Williams
7个回答

190

在Python中,反射比Java要容易得多并且更加灵活。

我建议阅读这个教程(在archive.org上)

没有直接的函数(据我所知)可以接受完全限定的类名并返回类,但是您有所有构建它所需的零件,并且您可以将它们连接在一起。

但是有一个建议:当您使用Python时,请不要尝试以Java风格编程。

如果您可以解释您尝试做什么,也许我们可以帮助您找到更加Pythonic的方法来完成它。

这里有一个可以实现您想要的功能的函数:

def get_class( kls ):
    parts = kls.split('.')
    module = ".".join(parts[:-1])
    m = __import__( module )
    for comp in parts[1:]:
        m = getattr(m, comp)            
    return m
你可以将这个函数的返回值视为类本身。以下是一个使用示例:
>>> D = get_class("datetime.datetime")
>>> D
<type 'datetime.datetime'>
>>> D.now()
datetime.datetime(2009, 1, 17, 2, 15, 58, 883000)
>>> a = D( 2010, 4, 22 )
>>> a
datetime.datetime(2010, 4, 22, 0, 0)
>>> 

这是如何运作的?

我们使用 __import__ 导入包含类的模块,这需要我们先从完整的名称中提取模块名。然后我们导入模块:

m = __import__( module )
在这种情况下,m仅仅是指顶级模块。 例如,如果你的类位于foo.baz模块中,则m将是foo模块。 我们可以使用getattr( m, 'baz' )轻松地获取到foo.baz的引用。 要从顶级模块获取类,必须在类名称的各个部分上递归使用getattr。 例如,假设你的类名为foo.baz.bar.Model,那么我们需要这样做:
m = __import__( "foo.baz.bar" ) #m is package foo
m = getattr( m, "baz" ) #m is package baz
m = getattr( m, "bar" ) #m is module bar
m = getattr( m, "Model" ) #m is class Model

以下是该循环中正在发生的事情:

for comp in parts[1:]:
    m = getattr(m, comp)    

循环结束时,m将成为该类的一个引用。这意味着m实际上是类本身,例如,您可以执行以下操作:

a = m() #instantiate a new instance of the class    
b = m( arg1, arg2 ) # pass arguments to the constructor

  

7
"Exec"非常危险。通过动态覆盖范围内的名称,你很容易弄乱自己,同样容易打开像罗德岛那么大的安全漏洞。 - Serafina Brocious
9
没错,这正是 __import__ 的存在意义。像 Django 这样进行模块加载魔法的项目经常使用它。不错的回答。 - cdleary
6
get_class = lambda name: reduce(getattr, name.split('.')[1:], __import__(name.partition('.')[0])) 可以翻译为“获取类 = lambda 名称: reduce(getattr, 名称.split('.')[1:], import(名称.partition('.')[0]))”,虽然 'object.from_name' 可能更好。例如:get_class('decimal.Decimal')、get_class(模块名称)。 - jfs
3
只需要定义一个七行的函数,真的很容易。 - Pavel Vlasov
1
请参考Pat的答案,他提供了一种使用importlib更加简洁的方法。 - sage88
显示剩余5条评论

30

假设这个类在你的作用域中:

globals()['classname'](args, to, constructor)

否则:

getattr(someModule, 'classname')(args, to, constructor)

编辑:注意,您不能像“ foo.bar”这样为getattr指定名称。 您需要通过.拆分它,并从左到右在每个片段上调用getattr()。 这将处理:

module, rest = 'foo.bar.baz'.split('.', 1)
fooBar = reduce(lambda a, b: getattr(a, b), rest.split('.'), globals()[module])
someVar = fooBar(args, to, constructor)

应该不是globals()['classname']吗? - orip
1
你是正确的。我忘了globals()不返回实际的全局作用域,只返回它的映射。进行如下编辑--谢谢! - Serafina Brocious
这与Java的Class.forName不等价,在Java中,不会对导入和未导入的内容做出任何假设。 - hasen
我的意思是,唯一的输入是以字符串格式表示的完全限定类名,例如“org.eclipse.something.utils.date.DateTimeWidget”。 - hasen
1
attrs = 'foo.bar.baz'.split('.'); fooBar = reduce(getattr, attrs[1:], __import__(attrs[0])) - jfs
1
或者 module,_,attrs = 'foo.bar.baz'.partition('.'); fooBar = reduce(getattr,attrs,__import__(module)) - jfs

22
def import_class_from_string(path):
    from importlib import import_module
    module_path, _, class_name = path.rpartition('.')
    mod = import_module(module_path)
    klass = getattr(mod, class_name)
    return klass

使用方法

In [59]: raise import_class_from_string('google.appengine.runtime.apiproxy_errors.DeadlineExceededError')()
---------------------------------------------------------------------------
DeadlineExceededError                     Traceback (most recent call last)
<ipython-input-59-b4e59d809b2f> in <module>()
----> 1 raise import_class_from_string('google.appengine.runtime.apiproxy_errors.DeadlineExceededError')()

DeadlineExceededError: 

1
使用importlib来完成这个任务是值得肯定的,因为它避免了需要递归查找类的问题(__import__会导致这个问题)。虽然文档不如被接受的答案详细,但更加简单和清晰。 - sage88

4
另一种实现。
def import_class(class_string):
    """Returns class object specified by a string.

    Args:
        class_string: The string representing a class.

    Raises:
        ValueError if module part of the class is not specified.
    """
    module_name, _, class_name = class_string.rpartition('.')
    if module_name == '':
        raise ValueError('Class name must contain module part.')
    return getattr(
        __import__(module_name, globals(), locals(), [class_name], -1),
        class_name)

if module_name 的 LBYL 很冗余,因为如果你只是删除这些行,你会得到的错误是:ValueError: Empty module name - bukzor

3

看起来你是从中间开始而不是从头开始。你真正想做什么?找到与给定字符串相关联的类只是达到目的的手段。

如果你能澄清问题,可能需要进行自我思维重构,更好的解决方案可能会呈现出来。

例如:你是否试图根据类型名称和一组参数加载保存的对象?Python将其称为反序列化,你应该查看pickle模块。即使反序列化过程恰好符合您描述的内容,您也不必担心其内部工作方式:

>>> class A(object):
...   def __init__(self, v):
...     self.v = v
...   def __reduce__(self):
...     return (self.__class__, (self.v,))
>>> a = A("example")
>>> import pickle
>>> b = pickle.loads(pickle.dumps(a))
>>> a.v, b.v
('example', 'example')
>>> a is b
False

1
这非常有趣,但与问题有些不同。 - Nick

2

这个方法在Python标准库中被称为unittest.TestLoader.loadTestsFromName。不幸的是,该方法继续执行其他与测试相关的活动,但是第一个部分看起来是可重用的。我已经编辑过它,以删除与测试相关的功能:

def get_object(name):
    """Retrieve a python object, given its dotted.name."""
    parts = name.split('.')
    parts_copy = parts[:]
    while parts_copy:
        try:
            module = __import__('.'.join(parts_copy))
            break
        except ImportError:
            del parts_copy[-1]
            if not parts_copy: raise
    parts = parts[1:]

    obj = module
    for part in parts:
        parent, obj = obj, getattr(obj, part)

    return obj

0

我需要获取 my_package 中所有现有类的对象。因此,我将所有必要的类导入到 my_package__init__.py 中。

因此,我的目录结构如下:

/my_package
    - __init__.py
    - module1.py
    - module2.py
    - module3.py

我的__init__.py看起来像这样:

from .module1 import ClassA
from .module2 import ClassB

然后我创建了一个像这样的函数:

def get_classes_from_module_name(module_name):
    return [_cls() for _, _cls in inspect.getmembers(__import__(module_name), inspect.isclass)]

module_name = 'my_package'

检查文档:https://docs.python.org/3/library/inspect.html#inspect.getmembers


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接