获取Python中类的类路径或命名空间,即使是嵌套的

8 投票
4 回答
4635 浏览
提问于 2025-04-16 08:40

我现在正在用Python写一个序列化模块,这个模块可以把用户定义的类进行序列化。为了做到这一点,我需要获取对象的完整命名空间,并把它写入一个文件。然后我可以用这个字符串来重新创建这个对象。

举个例子,假设我们在一个名为A.py的文件里有以下的类结构:

class B:
    class C:
        pass

现在假设my_klass_string是字符串"A::B::C"

klasses = my_klass_string.split("::")
if globals().has_key(klasses[0]):   
    klass = globals()[klasses[0]]
else:
    raise TypeError, "No class defined: %s} " % klasses[0]
if len(klasses) > 1:
    for klass_string in klasses:
        if klass.__dict__.has_key(klass_string):
            klass = klass.__dict__[klass_string]
        else:
            raise TypeError, "No class defined: %s} " % klass_string            
klass_obj = klass.__new__(klass)

我可以创建类C的一个实例,尽管它是在模块A中的类B下面。上面的代码相当于调用eval(klass_obj = A.B.C.__new__(A.B.C))

注意:我在这里使用__new__()是因为我正在重建一个序列化的对象,而我不知道这个类的__init__方法需要什么参数,所以我不想初始化这个对象。我想先创建对象而不调用init,然后再给它赋值。

我想知道有没有办法从一个字符串创建类A.B.C的对象。但是,反过来我该怎么做呢?我怎么才能从这个类的一个实例获取描述这个类完整路径的字符串,即使这个类是嵌套的?

4 个回答

1

我现在正在用Python写一个序列化模块,可以把用户自定义的类进行序列化。

别这样做。标准库里已经有现成的工具了。其实,如果你仔细算的话,至少有两个工具可以用(pickleshelve)。

6

你是无法以任何合理的方式做到这一点的,除非你想得很疯狂。我想你可以找到类名和模块名,然后对每个类名检查它是否存在于模块中。如果不存在,就得按照层级结构去检查模块中所有存在的类,直到找到为止。

不过,实际上没有必要有这样的类层级结构,所以这根本就不是个问题。:-)

另外,我知道你现在可能不想听这个,但:

跨平台的序列化是个有趣的话题,但用这样的对象来做可能没什么用,因为目标系统必须安装完全相同的对象层级。因此,你必须有两个用不同语言写的系统,它们必须是完全等价的。这几乎是不可能的,而且可能不值得去费这个劲。

举个例子,你不能使用Python标准库中的任何对象,因为那些在Ruby中是不存在的。最终结果是,你必须自己创建一个对象层级,最后只使用基本类型,比如字符串和数字。在这种情况下,你的对象实际上只是基本数据类型的容器,那么你完全可以用JSON或XML来序列化所有内容。

7

你无法通过一个类的实例来获取“类的完整路径”,因为在Python中并没有这个概念。举个例子:

>>> class B(object):
...     class C(object):
...             pass
... 
>>> D = B.C
>>> x = D()
>>> isinstance(x, B.C)
True

那么,x的“类路径”应该是什么呢?是D还是B.C?这两者都是有效的,因此Python并没有提供方法来区分它们。

实际上,连Python的pickle模块在处理对象x时也会遇到问题:

>>> import pickle
>>> t = open('/tmp/x.pickle', 'w+b')
>>> pickle.dump(x, t)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.6/pickle.py", line 1362, in dump
    Pickler(file, protocol).dump(obj)
  ...
  File "/usr/lib/python2.6/pickle.py", line 748, in save_global
   (obj, module, name))
  pickle.PicklingError: Can't pickle <class '__main__.C'>: it's not found as __main__.C

所以,总的来说,我认为唯一的办法就是给你所有的类添加一个属性(比如说_class_path),然后你的序列化代码就可以查找这个属性来记录类名到序列化格式中:

class A(object):
  _class_path = 'mymodule.A'
  class B(object):
    _class_path = 'mymodule.A.B'
    ...

你甚至可以通过一些元类的技巧来自动完成这个过程(不过也要看看同一个帖子里的其他评论,了解可能会遇到的问题,特别是如果你使用D=B.C这种方式)。

话虽如此,如果你能把你的序列化代码限制在(1)新式类的实例,以及(2)这些类是在模块的顶层定义的,那么你可以直接参考pickle的做法(在Python 2.6的pickle.py文件中,save_global函数的第730到768行)。

这个思路是,每个新式类都会定义__name____module__这两个属性,它们是字符串,分别代表类名(在源代码中找到的)和模块名(在sys.modules中找到的);通过保存这些信息,你可以在之后导入模块并获取类的实例:

__import__(module_name)
class_obj = getattr(sys.modules[module_name], class_name)

撰写回答