【问题标题】:Does python have an equivalent to Java Class.forName()?python 是否具有与 Java Class.forName() 等效的功能?
【发布时间】:2009-01-17 08:10:48
【问题描述】:

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


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

【问题讨论】:

标签: java python class instantiation


【解决方案1】:

python 中的反射比 Java 中的反射要容易得多,也灵活得多。

我推荐阅读这个tutorial

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

不过有一点建议:当你在 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的引用

要从顶层模块到类,必须在类名部分递归使用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 实际上是它的类,你可以这样做:

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

【讨论】:

  • Exec 是非常危险的。动态覆盖范围内的名称很容易自取其辱,而且打开一个罗德岛大小的安全漏洞也很容易。
  • 这里 exec 不安全的原因是你可以使用 ';'在类名中打破导入。这很容易允许任意代码执行。它可能损坏您的代码的原因是 exec 会覆盖冲突的名称,从而导致错误和/或安全漏洞。
  • 是的,这就是__import__ 的存在。像 Django 这样做模块加载魔法的项目一直都在使用它。很好的答案。
  • get_class = lambda name: reduce(getattr, name.split('.')[1:], __import__(name.partition('.')[0]))。虽然 'object.from_name' 可能是一个更好的名字。示例:get_class('decimal.Decimal')、get_class(module_name)。
  • 只需要定义一个七行函数。真正的轻松。
【解决方案2】:

假设课程在您的范围内:

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

否则:

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

编辑:注意,你不能给 getattr 取一个像 'foo.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'] 吗?
  • 你确实是对的。我忘了 globals() 不会返回实际的全局范围,只是它的映射。像这样编辑——谢谢!
  • 这不等同于java的Class.forName,在Java中,不做任何假设什么是导入什么不是
  • attrs = 'foo.bar.baz'.split('.'); fooBar = reduce(getattr, attrs[1:], __import__(attrs[0]))
  • module, _, attrs = 'foo.bar.baz'.partition('.'); fooBar = reduce(getattr, attrs, __import__(module))
【解决方案3】:
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 导致)递归地查找类。比公认的答案更简单、更清晰(尽管记录较少)。
【解决方案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
【解决方案5】:

您似乎是从中间而不是从头开始处理这个问题。你真正想做什么?找到与给定字符串关联的类是达到目的的一种手段。

如果你澄清你的问题,这可能需要你自己的精神重构,一个更好的解决方案可能会出现。

例如:您是否尝试根据类型名称和一组参数加载已保存的对象? Python 拼写了这个 unpickling,你应该看看pickle module。即使 unpickling 过程完全按照您的描述进行,您也不必担心它在内部是如何工作的:

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

【讨论】:

  • 这很有趣,但与问题有点不同
【解决方案6】:

这可以在 python 标准库中找到,作为 unittest.TestLoader.loadTestsFromName。不幸的是,该方法继续执行其他与测试相关的活动,但第一个 ha 看起来可重用。我已经对其进行了编辑以删除与测试相关的功能:

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

【讨论】:

    【解决方案7】:

    我需要获取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

    【讨论】:

      猜你喜欢
      • 2011-06-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-12
      • 1970-01-01
      • 2012-03-21
      相关资源
      最近更新 更多