python是否与Java Class.forName()等效?


95

我需要使用字符串参数并在Python中创建以该字符串命名的类的对象。在Java中,我会使用Class.forName().newInstance()。Python中是否有等效的东西?


感谢您的答复。回答那些想知道我在做什么的人:我想使用命令行参数作为类名,并实例化它。我实际上是在Jython编程并实例化Java类,因此是问题的Java实质。 getattr()效果很好。非常感谢。


stackoverflow.com/a/10675081/116891使用的一个例子importlib.import,将其从蟒3反向移植到2.7(docs.python.org/2/library/importlib.html
专利

Answers:


169

python中的反射比Java中的反射更容易,更灵活。

我建议阅读本教程

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

不过,有一点建议:使用python时不要尝试以Java风格进行编程。

如果您可以解释您要尝试的操作,也许我们可以帮助您找到一种更蟒蛇的方法。

这是一个执行您想要的功能的函数:

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
,我们可以很容易地获得一个参考foo.baz使用getattr( m, 'baz' )

要从顶层模块到达类,必须递归使用gettatr类名称的各个部分

举例来说,如果您的班级名称是,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实际上是itslef类,您可以例如执行以下操作:

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

6
Exec 非常危险。动态覆盖范围内的名称很容易使自己陷入困境,也很容易造成罗德岛大小的安全漏洞。
科迪·布罗维奇

1
exec这里不安全的原因是您可以使用';' 在类名中突出显示。这可以轻松地允许任意代码执行。它可能损坏您的代码的原因是exec将覆盖冲突的名称,从而导致错误和/或安全漏洞。
2009年

8
是的,这就是__import__存在的目的。像Django这样的项目,总是在做模块加载魔术。好答案。
09年

5
get_class = lambda name: reduce(getattr, name.split('.')[1:], __import__(name.partition('.')[0]))。虽然“ object.from_name”可能是一个更好的名称。示例:get_class('decimal.Decimal'),get_class(module_name)。
jfs

1
只需要定义一个七行函数。真正的轻松。
Pavel Vlasov'4

27

假设该类在您的范围内:

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

除此以外:

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

编辑:注意,您不能给'att.bar'这样的名称来获取属性。您需要将其分割为。并从左到右在每个块上调用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

你确实是对的。我忘记了globals()不会返回实际的全局范围,而只是返回它的映射。如此编辑-谢谢!
2009年

这不等同于Java的Class.forName,在Java中,不对导入的内容和不导入的内容进行任何假设
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

15
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
+1对于使用importlib进行此操作,因为它避免了(导致导入的)递归查找类的需要。比公认的答案更简单,更干净(尽管文献记载不足)。
sage88 '16

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_nameLBYL是相当redudant,因为你如果你只是删除这些线路的错误是:ValueError: Empty module name
bukzor 2014年

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

1

在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

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.